mirror of
https://github.com/restic/restic.git
synced 2026-08-03 20:52:16 +00:00
Update dependencies
Among others, this updates minio-go, so that the new "eu-west-3" zone for AWS is supported.
This commit is contained in:
+2
-3
@@ -17,16 +17,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/api/iterator"
|
||||
|
||||
"cloud.google.com/go/bigquery"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
Generated
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
package datatransfer
|
||||
|
||||
import (
|
||||
datatransferpb "google.golang.org/genproto/googleapis/cloud/bigquery/datatransfer/v1"
|
||||
)
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
var _ = fmt.Sprintf
|
||||
var _ = iterator.Done
|
||||
var _ = strconv.FormatUint
|
||||
var _ = time.Now
|
||||
|
||||
func TestDataTransferServiceSmoke(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping smoke test in short mode")
|
||||
}
|
||||
ctx := context.Background()
|
||||
ts := testutil.TokenSource(ctx, DefaultAuthScopes()...)
|
||||
if ts == nil {
|
||||
t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
|
||||
}
|
||||
|
||||
projectId := testutil.ProjID()
|
||||
_ = projectId
|
||||
|
||||
c, err := NewClient(ctx, option.WithTokenSource(ts))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s", projectId, "us-central1")
|
||||
var request = &datatransferpb.ListDataSourcesRequest{
|
||||
Parent: formattedParent,
|
||||
}
|
||||
|
||||
iter := c.ListDataSources(ctx, request)
|
||||
if _, err := iter.Next(); err != nil && err != iterator.Done {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
+601
@@ -0,0 +1,601 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
package datatransfer
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/version"
|
||||
gax "github.com/googleapis/gax-go"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/transport"
|
||||
datatransferpb "google.golang.org/genproto/googleapis/cloud/bigquery/datatransfer/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// CallOptions contains the retry settings for each method of Client.
|
||||
type CallOptions struct {
|
||||
GetDataSource []gax.CallOption
|
||||
ListDataSources []gax.CallOption
|
||||
CreateTransferConfig []gax.CallOption
|
||||
UpdateTransferConfig []gax.CallOption
|
||||
DeleteTransferConfig []gax.CallOption
|
||||
GetTransferConfig []gax.CallOption
|
||||
ListTransferConfigs []gax.CallOption
|
||||
ScheduleTransferRuns []gax.CallOption
|
||||
GetTransferRun []gax.CallOption
|
||||
DeleteTransferRun []gax.CallOption
|
||||
ListTransferRuns []gax.CallOption
|
||||
ListTransferLogs []gax.CallOption
|
||||
CheckValidCreds []gax.CallOption
|
||||
}
|
||||
|
||||
func defaultClientOptions() []option.ClientOption {
|
||||
return []option.ClientOption{
|
||||
option.WithEndpoint("bigquerydatatransfer.googleapis.com:443"),
|
||||
option.WithScopes(DefaultAuthScopes()...),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultCallOptions() *CallOptions {
|
||||
retry := map[[2]string][]gax.CallOption{
|
||||
{"default", "idempotent"}: {
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.DeadlineExceeded,
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.3,
|
||||
})
|
||||
}),
|
||||
},
|
||||
}
|
||||
return &CallOptions{
|
||||
GetDataSource: retry[[2]string{"default", "idempotent"}],
|
||||
ListDataSources: retry[[2]string{"default", "idempotent"}],
|
||||
CreateTransferConfig: retry[[2]string{"default", "non_idempotent"}],
|
||||
UpdateTransferConfig: retry[[2]string{"default", "non_idempotent"}],
|
||||
DeleteTransferConfig: retry[[2]string{"default", "idempotent"}],
|
||||
GetTransferConfig: retry[[2]string{"default", "idempotent"}],
|
||||
ListTransferConfigs: retry[[2]string{"default", "idempotent"}],
|
||||
ScheduleTransferRuns: retry[[2]string{"default", "non_idempotent"}],
|
||||
GetTransferRun: retry[[2]string{"default", "idempotent"}],
|
||||
DeleteTransferRun: retry[[2]string{"default", "idempotent"}],
|
||||
ListTransferRuns: retry[[2]string{"default", "idempotent"}],
|
||||
ListTransferLogs: retry[[2]string{"default", "idempotent"}],
|
||||
CheckValidCreds: retry[[2]string{"default", "idempotent"}],
|
||||
}
|
||||
}
|
||||
|
||||
// Client is a client for interacting with BigQuery Data Transfer API.
|
||||
type Client struct {
|
||||
// The connection to the service.
|
||||
conn *grpc.ClientConn
|
||||
|
||||
// The gRPC API client.
|
||||
client datatransferpb.DataTransferServiceClient
|
||||
|
||||
// The call options for this service.
|
||||
CallOptions *CallOptions
|
||||
|
||||
// The x-goog-* metadata to be sent with each request.
|
||||
xGoogMetadata metadata.MD
|
||||
}
|
||||
|
||||
// NewClient creates a new data transfer service client.
|
||||
//
|
||||
// The Google BigQuery Data Transfer Service API enables BigQuery users to
|
||||
// configure the transfer of their data from other Google Products into BigQuery.
|
||||
// This service contains methods that are end user exposed. It backs up the
|
||||
// frontend.
|
||||
func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
|
||||
conn, err := transport.DialGRPC(ctx, append(defaultClientOptions(), opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Client{
|
||||
conn: conn,
|
||||
CallOptions: defaultCallOptions(),
|
||||
|
||||
client: datatransferpb.NewDataTransferServiceClient(conn),
|
||||
}
|
||||
c.setGoogleClientInfo()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Connection returns the client's connection to the API service.
|
||||
func (c *Client) Connection() *grpc.ClientConn {
|
||||
return c.conn
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// the client is no longer required.
|
||||
func (c *Client) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// setGoogleClientInfo sets the name and version of the application in
|
||||
// the `x-goog-api-client` header passed on each request. Intended for
|
||||
// use by Google-written clients.
|
||||
func (c *Client) setGoogleClientInfo(keyval ...string) {
|
||||
kv := append([]string{"gl-go", version.Go()}, keyval...)
|
||||
kv = append(kv, "gapic", version.Repo, "gax", gax.Version, "grpc", grpc.Version)
|
||||
c.xGoogMetadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
|
||||
}
|
||||
|
||||
// GetDataSource retrieves a supported data source and returns its settings,
|
||||
// which can be used for UI rendering.
|
||||
func (c *Client) GetDataSource(ctx context.Context, req *datatransferpb.GetDataSourceRequest, opts ...gax.CallOption) (*datatransferpb.DataSource, error) {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.GetDataSource[0:len(c.CallOptions.GetDataSource):len(c.CallOptions.GetDataSource)], opts...)
|
||||
var resp *datatransferpb.DataSource
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.GetDataSource(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListDataSources lists supported data sources and returns their settings,
|
||||
// which can be used for UI rendering.
|
||||
func (c *Client) ListDataSources(ctx context.Context, req *datatransferpb.ListDataSourcesRequest, opts ...gax.CallOption) *DataSourceIterator {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.ListDataSources[0:len(c.CallOptions.ListDataSources):len(c.CallOptions.ListDataSources)], opts...)
|
||||
it := &DataSourceIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*datatransferpb.DataSource, string, error) {
|
||||
var resp *datatransferpb.ListDataSourcesResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.ListDataSources(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return resp.DataSources, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
return it
|
||||
}
|
||||
|
||||
// CreateTransferConfig creates a new data transfer configuration.
|
||||
func (c *Client) CreateTransferConfig(ctx context.Context, req *datatransferpb.CreateTransferConfigRequest, opts ...gax.CallOption) (*datatransferpb.TransferConfig, error) {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.CreateTransferConfig[0:len(c.CallOptions.CreateTransferConfig):len(c.CallOptions.CreateTransferConfig)], opts...)
|
||||
var resp *datatransferpb.TransferConfig
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.CreateTransferConfig(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UpdateTransferConfig updates a data transfer configuration.
|
||||
// All fields must be set, even if they are not updated.
|
||||
func (c *Client) UpdateTransferConfig(ctx context.Context, req *datatransferpb.UpdateTransferConfigRequest, opts ...gax.CallOption) (*datatransferpb.TransferConfig, error) {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.UpdateTransferConfig[0:len(c.CallOptions.UpdateTransferConfig):len(c.CallOptions.UpdateTransferConfig)], opts...)
|
||||
var resp *datatransferpb.TransferConfig
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.UpdateTransferConfig(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteTransferConfig deletes a data transfer configuration,
|
||||
// including any associated transfer runs and logs.
|
||||
func (c *Client) DeleteTransferConfig(ctx context.Context, req *datatransferpb.DeleteTransferConfigRequest, opts ...gax.CallOption) error {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.DeleteTransferConfig[0:len(c.CallOptions.DeleteTransferConfig):len(c.CallOptions.DeleteTransferConfig)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
_, err = c.client.DeleteTransferConfig(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTransferConfig returns information about a data transfer config.
|
||||
func (c *Client) GetTransferConfig(ctx context.Context, req *datatransferpb.GetTransferConfigRequest, opts ...gax.CallOption) (*datatransferpb.TransferConfig, error) {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.GetTransferConfig[0:len(c.CallOptions.GetTransferConfig):len(c.CallOptions.GetTransferConfig)], opts...)
|
||||
var resp *datatransferpb.TransferConfig
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.GetTransferConfig(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListTransferConfigs returns information about all data transfers in the project.
|
||||
func (c *Client) ListTransferConfigs(ctx context.Context, req *datatransferpb.ListTransferConfigsRequest, opts ...gax.CallOption) *TransferConfigIterator {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.ListTransferConfigs[0:len(c.CallOptions.ListTransferConfigs):len(c.CallOptions.ListTransferConfigs)], opts...)
|
||||
it := &TransferConfigIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*datatransferpb.TransferConfig, string, error) {
|
||||
var resp *datatransferpb.ListTransferConfigsResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.ListTransferConfigs(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return resp.TransferConfigs, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
return it
|
||||
}
|
||||
|
||||
// ScheduleTransferRuns creates transfer runs for a time range [range_start_time, range_end_time].
|
||||
// For each date - or whatever granularity the data source supports - in the
|
||||
// range, one transfer run is created.
|
||||
// Note that runs are created per UTC time in the time range.
|
||||
func (c *Client) ScheduleTransferRuns(ctx context.Context, req *datatransferpb.ScheduleTransferRunsRequest, opts ...gax.CallOption) (*datatransferpb.ScheduleTransferRunsResponse, error) {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.ScheduleTransferRuns[0:len(c.CallOptions.ScheduleTransferRuns):len(c.CallOptions.ScheduleTransferRuns)], opts...)
|
||||
var resp *datatransferpb.ScheduleTransferRunsResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.ScheduleTransferRuns(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetTransferRun returns information about the particular transfer run.
|
||||
func (c *Client) GetTransferRun(ctx context.Context, req *datatransferpb.GetTransferRunRequest, opts ...gax.CallOption) (*datatransferpb.TransferRun, error) {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.GetTransferRun[0:len(c.CallOptions.GetTransferRun):len(c.CallOptions.GetTransferRun)], opts...)
|
||||
var resp *datatransferpb.TransferRun
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.GetTransferRun(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteTransferRun deletes the specified transfer run.
|
||||
func (c *Client) DeleteTransferRun(ctx context.Context, req *datatransferpb.DeleteTransferRunRequest, opts ...gax.CallOption) error {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.DeleteTransferRun[0:len(c.CallOptions.DeleteTransferRun):len(c.CallOptions.DeleteTransferRun)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
_, err = c.client.DeleteTransferRun(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
// ListTransferRuns returns information about running and completed jobs.
|
||||
func (c *Client) ListTransferRuns(ctx context.Context, req *datatransferpb.ListTransferRunsRequest, opts ...gax.CallOption) *TransferRunIterator {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.ListTransferRuns[0:len(c.CallOptions.ListTransferRuns):len(c.CallOptions.ListTransferRuns)], opts...)
|
||||
it := &TransferRunIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*datatransferpb.TransferRun, string, error) {
|
||||
var resp *datatransferpb.ListTransferRunsResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.ListTransferRuns(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return resp.TransferRuns, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
return it
|
||||
}
|
||||
|
||||
// ListTransferLogs returns user facing log messages for the data transfer run.
|
||||
func (c *Client) ListTransferLogs(ctx context.Context, req *datatransferpb.ListTransferLogsRequest, opts ...gax.CallOption) *TransferMessageIterator {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.ListTransferLogs[0:len(c.CallOptions.ListTransferLogs):len(c.CallOptions.ListTransferLogs)], opts...)
|
||||
it := &TransferMessageIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*datatransferpb.TransferMessage, string, error) {
|
||||
var resp *datatransferpb.ListTransferLogsResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.ListTransferLogs(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return resp.TransferMessages, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
return it
|
||||
}
|
||||
|
||||
// CheckValidCreds returns true if valid credentials exist for the given data source and
|
||||
// requesting user.
|
||||
// Some data sources doesn't support service account, so we need to talk to
|
||||
// them on behalf of the end user. This API just checks whether we have OAuth
|
||||
// token for the particular user, which is a pre-requisite before user can
|
||||
// create a transfer config.
|
||||
func (c *Client) CheckValidCreds(ctx context.Context, req *datatransferpb.CheckValidCredsRequest, opts ...gax.CallOption) (*datatransferpb.CheckValidCredsResponse, error) {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.CheckValidCreds[0:len(c.CallOptions.CheckValidCreds):len(c.CallOptions.CheckValidCreds)], opts...)
|
||||
var resp *datatransferpb.CheckValidCredsResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.CheckValidCreds(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DataSourceIterator manages a stream of *datatransferpb.DataSource.
|
||||
type DataSourceIterator struct {
|
||||
items []*datatransferpb.DataSource
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*datatransferpb.DataSource, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *DataSourceIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *DataSourceIterator) Next() (*datatransferpb.DataSource, error) {
|
||||
var item *datatransferpb.DataSource
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *DataSourceIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *DataSourceIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
||||
|
||||
// TransferConfigIterator manages a stream of *datatransferpb.TransferConfig.
|
||||
type TransferConfigIterator struct {
|
||||
items []*datatransferpb.TransferConfig
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*datatransferpb.TransferConfig, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *TransferConfigIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *TransferConfigIterator) Next() (*datatransferpb.TransferConfig, error) {
|
||||
var item *datatransferpb.TransferConfig
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *TransferConfigIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *TransferConfigIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
||||
|
||||
// TransferMessageIterator manages a stream of *datatransferpb.TransferMessage.
|
||||
type TransferMessageIterator struct {
|
||||
items []*datatransferpb.TransferMessage
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*datatransferpb.TransferMessage, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *TransferMessageIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *TransferMessageIterator) Next() (*datatransferpb.TransferMessage, error) {
|
||||
var item *datatransferpb.TransferMessage
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *TransferMessageIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *TransferMessageIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
||||
|
||||
// TransferRunIterator manages a stream of *datatransferpb.TransferRun.
|
||||
type TransferRunIterator struct {
|
||||
items []*datatransferpb.TransferRun
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*datatransferpb.TransferRun, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *TransferRunIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *TransferRunIterator) Next() (*datatransferpb.TransferRun, error) {
|
||||
var item *datatransferpb.TransferRun
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *TransferRunIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *TransferRunIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
||||
Generated
Vendored
+288
@@ -0,0 +1,288 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
package datatransfer_test
|
||||
|
||||
import (
|
||||
"cloud.google.com/go/bigquery/datatransfer/apiv1"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
datatransferpb "google.golang.org/genproto/googleapis/cloud/bigquery/datatransfer/v1"
|
||||
)
|
||||
|
||||
func ExampleNewClient() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use client.
|
||||
_ = c
|
||||
}
|
||||
|
||||
func ExampleClient_GetDataSource() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.GetDataSourceRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.GetDataSource(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExampleClient_ListDataSources() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.ListDataSourcesRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListDataSources(ctx, req)
|
||||
for {
|
||||
resp, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_CreateTransferConfig() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.CreateTransferConfigRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.CreateTransferConfig(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExampleClient_UpdateTransferConfig() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.UpdateTransferConfigRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.UpdateTransferConfig(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExampleClient_DeleteTransferConfig() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.DeleteTransferConfigRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.DeleteTransferConfig(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_GetTransferConfig() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.GetTransferConfigRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.GetTransferConfig(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExampleClient_ListTransferConfigs() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.ListTransferConfigsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListTransferConfigs(ctx, req)
|
||||
for {
|
||||
resp, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_ScheduleTransferRuns() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.ScheduleTransferRunsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.ScheduleTransferRuns(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExampleClient_GetTransferRun() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.GetTransferRunRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.GetTransferRun(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExampleClient_DeleteTransferRun() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.DeleteTransferRunRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.DeleteTransferRun(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_ListTransferRuns() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.ListTransferRunsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListTransferRuns(ctx, req)
|
||||
for {
|
||||
resp, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_ListTransferLogs() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.ListTransferLogsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListTransferLogs(ctx, req)
|
||||
for {
|
||||
resp, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_CheckValidCreds() {
|
||||
ctx := context.Background()
|
||||
c, err := datatransfer.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &datatransferpb.CheckValidCredsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.CheckValidCreds(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
// Package datatransfer is an auto-generated package for the
|
||||
// BigQuery Data Transfer API.
|
||||
//
|
||||
// NOTE: This package is in alpha. It is not stable, and is likely to change.
|
||||
//
|
||||
// Transfers data from partner SaaS applications to Google BigQuery on a
|
||||
// scheduled, managed basis.
|
||||
package datatransfer // import "cloud.google.com/go/bigquery/datatransfer/apiv1"
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
|
||||
out, _ := metadata.FromOutgoingContext(ctx)
|
||||
out = out.Copy()
|
||||
for _, md := range mds {
|
||||
for k, v := range md {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return metadata.NewOutgoingContext(ctx, out)
|
||||
}
|
||||
|
||||
// DefaultAuthScopes reports the default set of authentication scopes to use with this package.
|
||||
func DefaultAuthScopes() []string {
|
||||
return []string{
|
||||
"https://www.googleapis.com/auth/bigquery",
|
||||
"https://www.googleapis.com/auth/cloud-platform",
|
||||
"https://www.googleapis.com/auth/cloud-platform.read-only",
|
||||
}
|
||||
}
|
||||
+1146
File diff suppressed because it is too large
Load Diff
+135
@@ -0,0 +1,135 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package datatransfer
|
||||
|
||||
// ProjectPath returns the path for the project resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s", project)
|
||||
// instead.
|
||||
func ProjectPath(project string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
""
|
||||
}
|
||||
|
||||
// LocationPath returns the path for the location resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s/locations/%s", project, location)
|
||||
// instead.
|
||||
func LocationPath(project, location string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/locations/" +
|
||||
location +
|
||||
""
|
||||
}
|
||||
|
||||
// LocationDataSourcePath returns the path for the location data source resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s/locations/%s/dataSources/%s", project, location, dataSource)
|
||||
// instead.
|
||||
func LocationDataSourcePath(project, location, dataSource string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/locations/" +
|
||||
location +
|
||||
"/dataSources/" +
|
||||
dataSource +
|
||||
""
|
||||
}
|
||||
|
||||
// LocationTransferConfigPath returns the path for the location transfer config resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s", project, location, transferConfig)
|
||||
// instead.
|
||||
func LocationTransferConfigPath(project, location, transferConfig string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/locations/" +
|
||||
location +
|
||||
"/transferConfigs/" +
|
||||
transferConfig +
|
||||
""
|
||||
}
|
||||
|
||||
// LocationRunPath returns the path for the location run resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s/runs/%s", project, location, transferConfig, run)
|
||||
// instead.
|
||||
func LocationRunPath(project, location, transferConfig, run string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/locations/" +
|
||||
location +
|
||||
"/transferConfigs/" +
|
||||
transferConfig +
|
||||
"/runs/" +
|
||||
run +
|
||||
""
|
||||
}
|
||||
|
||||
// DataSourcePath returns the path for the data source resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s/dataSources/%s", project, dataSource)
|
||||
// instead.
|
||||
func DataSourcePath(project, dataSource string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/dataSources/" +
|
||||
dataSource +
|
||||
""
|
||||
}
|
||||
|
||||
// TransferConfigPath returns the path for the transfer config resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s/transferConfigs/%s", project, transferConfig)
|
||||
// instead.
|
||||
func TransferConfigPath(project, transferConfig string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/transferConfigs/" +
|
||||
transferConfig +
|
||||
""
|
||||
}
|
||||
|
||||
// RunPath returns the path for the run resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s/transferConfigs/%s/runs/%s", project, transferConfig, run)
|
||||
// instead.
|
||||
func RunPath(project, transferConfig, run string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/transferConfigs/" +
|
||||
transferConfig +
|
||||
"/runs/" +
|
||||
run +
|
||||
""
|
||||
}
|
||||
+15
-8
@@ -150,8 +150,9 @@ There are two ways to construct schemas with this package.
|
||||
You can build a schema by hand, like so:
|
||||
|
||||
schema1 := bigquery.Schema{
|
||||
&bigquery.FieldSchema{Name: "Name", Required: true, Type: bigquery.StringFieldType},
|
||||
&bigquery.FieldSchema{Name: "Grades", Repeated: true, Type: bigquery.IntegerFieldType},
|
||||
{Name: "Name", Required: true, Type: bigquery.StringFieldType},
|
||||
{Name: "Grades", Repeated: true, Type: bigquery.IntegerFieldType},
|
||||
{Name: "Optional", Required: false, Type: bigquery.IntegerFieldType},
|
||||
}
|
||||
|
||||
Or you can infer the schema from a struct:
|
||||
@@ -159,6 +160,7 @@ Or you can infer the schema from a struct:
|
||||
type student struct {
|
||||
Name string
|
||||
Grades []int
|
||||
Optional bigquery.NullInt64
|
||||
}
|
||||
schema2, err := bigquery.InferSchema(student{})
|
||||
if err != nil {
|
||||
@@ -166,19 +168,24 @@ Or you can infer the schema from a struct:
|
||||
}
|
||||
// schema1 and schema2 are identical.
|
||||
|
||||
Struct inference supports tags like those of the encoding/json package,
|
||||
so you can change names or ignore fields:
|
||||
Struct inference supports tags like those of the encoding/json package, so you can
|
||||
change names, ignore fields, or mark a field as nullable (non-required). Fields
|
||||
declared as on of the Null types (NullInt64, NullFloat64, NullString, NullBool,
|
||||
NullTimestamp, NullDate, NullTime and NullDateTime) are automatically inferred as
|
||||
nullable, so the "nullable" tag is only needed for []byte and pointer-to-struct
|
||||
fields.
|
||||
|
||||
type student2 struct {
|
||||
Name string `bigquery:"full_name"`
|
||||
Grades []int
|
||||
Secret string `bigquery:"-"`
|
||||
Name string `bigquery:"full_name"`
|
||||
Grades []int
|
||||
Secret string `bigquery:"-"`
|
||||
Optional []byte `bigquery:",nullable"
|
||||
}
|
||||
schema3, err := bigquery.InferSchema(student2{})
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// schema3 has fields "full_name" and "Grade".
|
||||
// schema3 has required fields "full_name" and "Grade", and nullable BYTES field "Optional".
|
||||
|
||||
Having constructed a schema, you can create a table with it like so:
|
||||
|
||||
|
||||
+36
-8
@@ -395,10 +395,12 @@ func ExampleInferSchema() {
|
||||
|
||||
func ExampleInferSchema_tags() {
|
||||
type Item struct {
|
||||
Name string
|
||||
Size float64
|
||||
Count int `bigquery:"number"`
|
||||
Secret []byte `bigquery:"-"`
|
||||
Name string
|
||||
Size float64
|
||||
Count int `bigquery:"number"`
|
||||
Secret []byte `bigquery:"-"`
|
||||
Optional bigquery.NullBool
|
||||
OptBytes []byte `bigquery:",nullable"`
|
||||
}
|
||||
schema, err := bigquery.InferSchema(Item{})
|
||||
if err != nil {
|
||||
@@ -406,12 +408,14 @@ func ExampleInferSchema_tags() {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
for _, fs := range schema {
|
||||
fmt.Println(fs.Name, fs.Type)
|
||||
fmt.Println(fs.Name, fs.Type, fs.Required)
|
||||
}
|
||||
// Output:
|
||||
// Name STRING
|
||||
// Size FLOAT
|
||||
// number INTEGER
|
||||
// Name STRING true
|
||||
// Size FLOAT true
|
||||
// number INTEGER true
|
||||
// Optional BOOLEAN false
|
||||
// OptBytes BYTES false
|
||||
}
|
||||
|
||||
func ExampleTable_Create() {
|
||||
@@ -755,3 +759,27 @@ func ExampleUploader_Put_struct() {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleUploader_Put_valuesSaver() {
|
||||
ctx := context.Background()
|
||||
client, err := bigquery.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
u := client.Dataset("my_dataset").Table("my_table").Uploader()
|
||||
|
||||
var vss []*bigquery.ValuesSaver
|
||||
for i, name := range []string{"n1", "n2", "n3"} {
|
||||
// Assume schema holds the table's schema.
|
||||
vss = append(vss, &bigquery.ValuesSaver{
|
||||
Schema: schema,
|
||||
InsertID: name,
|
||||
Row: []bigquery.Value{name, int64(i)},
|
||||
})
|
||||
}
|
||||
|
||||
if err := u.Put(ctx, vss); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
+123
-27
@@ -53,8 +53,10 @@ var (
|
||||
}},
|
||||
}
|
||||
testTableExpiration time.Time
|
||||
datasetIDs = testutil.NewUIDSpaceSep("dataset", '_')
|
||||
tableIDs = testutil.NewUIDSpaceSep("table", '_')
|
||||
// BigQuery does not accept hyphens in dataset or table IDs, so we create IDs
|
||||
// with underscores.
|
||||
datasetIDs = testutil.NewUIDSpaceSep("dataset", '_')
|
||||
tableIDs = testutil.NewUIDSpaceSep("table", '_')
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -94,8 +96,6 @@ func initIntegrationTest() func() {
|
||||
if err != nil {
|
||||
log.Fatalf("storage.NewClient: %v", err)
|
||||
}
|
||||
// BigQuery does not accept hyphens in dataset or table IDs, so we create IDs
|
||||
// with underscores.
|
||||
dataset = client.Dataset(datasetIDs.New())
|
||||
if err := dataset.Create(ctx, nil); err != nil {
|
||||
log.Fatalf("creating dataset %s: %v", dataset.DatasetID, err)
|
||||
@@ -197,16 +197,28 @@ func TestIntegration_TableMetadata(t *testing.T) {
|
||||
|
||||
// Create tables that have time partitioning
|
||||
partitionCases := []struct {
|
||||
timePartitioning TimePartitioning
|
||||
expectedExpiration time.Duration
|
||||
timePartitioning TimePartitioning
|
||||
wantExpiration time.Duration
|
||||
wantField string
|
||||
}{
|
||||
{TimePartitioning{}, time.Duration(0)},
|
||||
{TimePartitioning{time.Second}, time.Second},
|
||||
{TimePartitioning{}, time.Duration(0), ""},
|
||||
{TimePartitioning{Expiration: time.Second}, time.Second, ""},
|
||||
{
|
||||
TimePartitioning{
|
||||
Expiration: time.Second,
|
||||
Field: "date",
|
||||
}, time.Second, "date"},
|
||||
}
|
||||
|
||||
schema2 := Schema{
|
||||
{Name: "name", Type: StringFieldType},
|
||||
{Name: "date", Type: DateFieldType},
|
||||
}
|
||||
|
||||
for i, c := range partitionCases {
|
||||
table := dataset.Table(fmt.Sprintf("t_metadata_partition_%v", i))
|
||||
err = table.Create(context.Background(), &TableMetadata{
|
||||
Schema: schema,
|
||||
Schema: schema2,
|
||||
TimePartitioning: &c.timePartitioning,
|
||||
ExpirationTime: time.Now().Add(5 * time.Minute),
|
||||
})
|
||||
@@ -220,7 +232,10 @@ func TestIntegration_TableMetadata(t *testing.T) {
|
||||
}
|
||||
|
||||
got := md.TimePartitioning
|
||||
want := &TimePartitioning{c.expectedExpiration}
|
||||
want := &TimePartitioning{
|
||||
Expiration: c.wantExpiration,
|
||||
Field: c.wantField,
|
||||
}
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Errorf("metadata.TimePartitioning: got %v, want %v", got, want)
|
||||
}
|
||||
@@ -249,7 +264,7 @@ func TestIntegration_DatasetCreate(t *testing.T) {
|
||||
t.Errorf("location: got %q, want %q", got, want)
|
||||
}
|
||||
if err := ds.Delete(ctx); err != nil {
|
||||
t.Fatalf("deleting dataset %s: %v", ds, err)
|
||||
t.Fatalf("deleting dataset %v: %v", ds, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,7 +600,7 @@ func TestIntegration_UploadAndRead(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test reading directly into a []Value.
|
||||
valueLists, err := readAll(table.Read(ctx))
|
||||
valueLists, schema, _, err := readAll(table.Read(ctx))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -595,6 +610,9 @@ func TestIntegration_UploadAndRead(t *testing.T) {
|
||||
if err := it.Next(&got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testutil.Equal(it.Schema, schema) {
|
||||
t.Fatalf("got schema %v, want %v", it.Schema, schema)
|
||||
}
|
||||
want := []Value(vl)
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Errorf("%d: got %v, want %v", i, got, want)
|
||||
@@ -656,6 +674,10 @@ type TestStruct struct {
|
||||
RecordArray []SubTestStruct
|
||||
}
|
||||
|
||||
// Round times to the microsecond for comparison purposes.
|
||||
var roundToMicros = cmp.Transformer("RoundToMicros",
|
||||
func(t time.Time) time.Time { return t.Round(time.Microsecond) })
|
||||
|
||||
func TestIntegration_UploadAndReadStructs(t *testing.T) {
|
||||
if client == nil {
|
||||
t.Skip("Integration tests skipped")
|
||||
@@ -755,15 +777,11 @@ func TestIntegration_UploadAndReadStructs(t *testing.T) {
|
||||
}
|
||||
sort.Sort(byName(got))
|
||||
|
||||
// Compare times to the microsecond.
|
||||
timeEq := func(x, y time.Time) bool {
|
||||
return x.Round(time.Microsecond).Equal(y.Round(time.Microsecond))
|
||||
}
|
||||
// BigQuery does not elide nils. It reports an error for nil fields.
|
||||
for i, g := range got {
|
||||
if i >= len(want) {
|
||||
t.Errorf("%d: got %v, past end of want", i, pretty.Value(g))
|
||||
} else if diff := testutil.Diff(g, want[i], cmp.Comparer(timeEq)); diff != "" {
|
||||
} else if diff := testutil.Diff(g, want[i], roundToMicros); diff != "" {
|
||||
t.Errorf("%d: got=-, want=+:\n%s", i, diff)
|
||||
}
|
||||
}
|
||||
@@ -775,6 +793,69 @@ func (b byName) Len() int { return len(b) }
|
||||
func (b byName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||
func (b byName) Less(i, j int) bool { return b[i].Name < b[j].Name }
|
||||
|
||||
func TestIntegration_UploadAndReadNullable(t *testing.T) {
|
||||
if client == nil {
|
||||
t.Skip("Integration tests skipped")
|
||||
}
|
||||
ctm := civil.Time{15, 4, 5, 6000}
|
||||
cdt := civil.DateTime{testDate, ctm}
|
||||
testUploadAndReadNullable(t, testStructNullable{}, make([]Value, len(testStructNullableSchema)))
|
||||
testUploadAndReadNullable(t, testStructNullable{
|
||||
String: NullString{"x", true},
|
||||
Bytes: []byte{1, 2, 3},
|
||||
Integer: NullInt64{1, true},
|
||||
Float: NullFloat64{2.3, true},
|
||||
Boolean: NullBool{true, true},
|
||||
Timestamp: NullTimestamp{testTimestamp, true},
|
||||
Date: NullDate{testDate, true},
|
||||
Time: NullTime{ctm, true},
|
||||
DateTime: NullDateTime{cdt, true},
|
||||
Record: &subNullable{X: NullInt64{4, true}},
|
||||
},
|
||||
[]Value{"x", []byte{1, 2, 3}, int64(1), 2.3, true, testTimestamp, testDate, ctm, cdt, []Value{int64(4)}})
|
||||
}
|
||||
|
||||
func testUploadAndReadNullable(t *testing.T, ts testStructNullable, wantRow []Value) {
|
||||
ctx := context.Background()
|
||||
table := newTable(t, testStructNullableSchema)
|
||||
defer table.Delete(ctx)
|
||||
|
||||
// Populate the table.
|
||||
upl := table.Uploader()
|
||||
if err := upl.Put(ctx, []*StructSaver{{Schema: testStructNullableSchema, Struct: ts}}); err != nil {
|
||||
t.Fatal(putError(err))
|
||||
}
|
||||
// Wait until the data has been uploaded. This can take a few seconds, according
|
||||
// to https://cloud.google.com/bigquery/streaming-data-into-bigquery.
|
||||
if err := waitForRow(ctx, table); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read into a []Value.
|
||||
iter := table.Read(ctx)
|
||||
gotRows, _, _, err := readAll(iter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(gotRows) != 1 {
|
||||
t.Fatalf("got %d rows, want 1", len(gotRows))
|
||||
}
|
||||
if diff := testutil.Diff(gotRows[0], wantRow, roundToMicros); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
|
||||
// Read into a struct.
|
||||
want := ts
|
||||
var sn testStructNullable
|
||||
it := table.Read(ctx)
|
||||
if err := it.Next(&sn); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := testutil.Diff(sn, want, roundToMicros); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_TableUpdate(t *testing.T) {
|
||||
if client == nil {
|
||||
t.Skip("Integration tests skipped")
|
||||
@@ -940,7 +1021,7 @@ func TestIntegration_Load(t *testing.T) {
|
||||
if err := wait(ctx, job); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
checkRead(t, "reader load", table.Read(ctx), wantRows)
|
||||
checkReadAndTotalRows(t, "reader load", table.Read(ctx), wantRows)
|
||||
|
||||
}
|
||||
|
||||
@@ -1270,7 +1351,7 @@ func TestIntegration_ExtractExternal(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
checkRead(t, "external query", iter, wantRows)
|
||||
checkReadAndTotalRows(t, "external query", iter, wantRows)
|
||||
|
||||
// Make a table pointing to the file, and query it.
|
||||
// BigQuery does not allow a Table.Read on an external table.
|
||||
@@ -1288,7 +1369,7 @@ func TestIntegration_ExtractExternal(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
checkRead(t, "external table", iter, wantRows)
|
||||
checkReadAndTotalRows(t, "external table", iter, wantRows)
|
||||
|
||||
// While we're here, check that the table metadata is correct.
|
||||
md, err := table.Metadata(ctx)
|
||||
@@ -1452,19 +1533,28 @@ func newTable(t *testing.T, s Schema) *Table {
|
||||
}
|
||||
|
||||
func checkRead(t *testing.T, msg string, it *RowIterator, want [][]Value) {
|
||||
if msg2, ok := compareRead(it, want); !ok {
|
||||
if msg2, ok := compareRead(it, want, false); !ok {
|
||||
t.Errorf("%s: %s", msg, msg2)
|
||||
}
|
||||
}
|
||||
|
||||
func compareRead(it *RowIterator, want [][]Value) (msg string, ok bool) {
|
||||
got, err := readAll(it)
|
||||
func checkReadAndTotalRows(t *testing.T, msg string, it *RowIterator, want [][]Value) {
|
||||
if msg2, ok := compareRead(it, want, true); !ok {
|
||||
t.Errorf("%s: %s", msg, msg2)
|
||||
}
|
||||
}
|
||||
|
||||
func compareRead(it *RowIterator, want [][]Value, compareTotalRows bool) (msg string, ok bool) {
|
||||
got, _, totalRows, err := readAll(it)
|
||||
if err != nil {
|
||||
return err.Error(), false
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
return fmt.Sprintf("got %d rows, want %d", len(got), len(want)), false
|
||||
}
|
||||
if compareTotalRows && len(got) != int(totalRows) {
|
||||
return fmt.Sprintf("got %d rows, but totalRows = %d", len(got), totalRows), false
|
||||
}
|
||||
sort.Sort(byCol0(got))
|
||||
for i, r := range got {
|
||||
gotRow := []Value(r)
|
||||
@@ -1476,18 +1566,24 @@ func compareRead(it *RowIterator, want [][]Value) (msg string, ok bool) {
|
||||
return "", true
|
||||
}
|
||||
|
||||
func readAll(it *RowIterator) ([][]Value, error) {
|
||||
var rows [][]Value
|
||||
func readAll(it *RowIterator) ([][]Value, Schema, uint64, error) {
|
||||
var (
|
||||
rows [][]Value
|
||||
schema Schema
|
||||
totalRows uint64
|
||||
)
|
||||
for {
|
||||
var vals []Value
|
||||
err := it.Next(&vals)
|
||||
if err == iterator.Done {
|
||||
return rows, nil
|
||||
return rows, schema, totalRows, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
rows = append(rows, vals)
|
||||
schema = it.Schema
|
||||
totalRows = it.TotalRows
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-8
@@ -48,9 +48,14 @@ type RowIterator struct {
|
||||
// is also set, StartIndex is ignored.
|
||||
StartIndex uint64
|
||||
|
||||
rows [][]Value
|
||||
// The schema of the table. Available after the first call to Next.
|
||||
Schema Schema
|
||||
|
||||
schema Schema // populated on first call to fetch
|
||||
// The total number of rows in the result. Available after the first call to Next.
|
||||
// May be zero just after rows were inserted.
|
||||
TotalRows uint64
|
||||
|
||||
rows [][]Value
|
||||
structLoader structLoader // used to populate a pointer to a struct
|
||||
}
|
||||
|
||||
@@ -88,8 +93,11 @@ type RowIterator struct {
|
||||
// type (RECORD or nested schema) corresponds to a nested struct or struct pointer.
|
||||
// All calls to Next on the same iterator must use the same struct type.
|
||||
//
|
||||
// It is an error to attempt to read a BigQuery NULL value into a struct field.
|
||||
// If your table contains NULLs, use a *[]Value or *map[string]Value.
|
||||
// It is an error to attempt to read a BigQuery NULL value into a struct field,
|
||||
// unless the field is of type []byte or is one of the special Null types: NullInt64,
|
||||
// NullFloat64, NullBool, NullString, NullTimestamp, NullDate, NullTime or
|
||||
// NullDateTime. You can also use a *[]Value or *map[string]Value to read from a
|
||||
// table with NULLs.
|
||||
func (it *RowIterator) Next(dst interface{}) error {
|
||||
var vl ValueLoader
|
||||
switch dst := dst.(type) {
|
||||
@@ -113,12 +121,12 @@ func (it *RowIterator) Next(dst interface{}) error {
|
||||
if vl == nil {
|
||||
// This can only happen if dst is a pointer to a struct. We couldn't
|
||||
// set vl above because we need the schema.
|
||||
if err := it.structLoader.set(dst, it.schema); err != nil {
|
||||
if err := it.structLoader.set(dst, it.Schema); err != nil {
|
||||
return err
|
||||
}
|
||||
vl = &it.structLoader
|
||||
}
|
||||
return vl.Load(row, it.schema)
|
||||
return vl.Load(row, it.Schema)
|
||||
}
|
||||
|
||||
func isStructPtr(x interface{}) bool {
|
||||
@@ -130,12 +138,13 @@ func isStructPtr(x interface{}) bool {
|
||||
func (it *RowIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
|
||||
|
||||
func (it *RowIterator) fetch(pageSize int, pageToken string) (string, error) {
|
||||
res, err := it.pf(it.ctx, it.table, it.schema, it.StartIndex, int64(pageSize), pageToken)
|
||||
res, err := it.pf(it.ctx, it.table, it.Schema, it.StartIndex, int64(pageSize), pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.rows = append(it.rows, res.rows...)
|
||||
it.schema = res.schema
|
||||
it.Schema = res.schema
|
||||
it.TotalRows = res.totalRows
|
||||
return res.pageToken, nil
|
||||
}
|
||||
|
||||
|
||||
+28
-26
@@ -64,6 +64,7 @@ func TestIterator(t *testing.T) {
|
||||
want [][]Value
|
||||
wantErr error
|
||||
wantSchema Schema
|
||||
wantTotalRows uint64
|
||||
}{
|
||||
{
|
||||
desc: "Iteration over single empty page",
|
||||
@@ -87,11 +88,13 @@ func TestIterator(t *testing.T) {
|
||||
pageToken: "",
|
||||
rows: [][]Value{{1, 2}, {11, 12}},
|
||||
schema: iiSchema,
|
||||
totalRows: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: [][]Value{{1, 2}, {11, 12}},
|
||||
wantSchema: iiSchema,
|
||||
want: [][]Value{{1, 2}, {11, 12}},
|
||||
wantSchema: iiSchema,
|
||||
wantTotalRows: 4,
|
||||
},
|
||||
{
|
||||
desc: "Iteration over single page with different schema",
|
||||
@@ -115,6 +118,7 @@ func TestIterator(t *testing.T) {
|
||||
pageToken: "a",
|
||||
rows: [][]Value{{1, 2}, {11, 12}},
|
||||
schema: iiSchema,
|
||||
totalRows: 4,
|
||||
},
|
||||
},
|
||||
"a": {
|
||||
@@ -122,11 +126,13 @@ func TestIterator(t *testing.T) {
|
||||
pageToken: "",
|
||||
rows: [][]Value{{101, 102}, {111, 112}},
|
||||
schema: iiSchema,
|
||||
totalRows: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: [][]Value{{1, 2}, {11, 12}, {101, 102}, {111, 112}},
|
||||
wantSchema: iiSchema,
|
||||
want: [][]Value{{1, 2}, {11, 12}, {101, 102}, {111, 112}},
|
||||
wantSchema: iiSchema,
|
||||
wantTotalRows: 4,
|
||||
},
|
||||
{
|
||||
desc: "Server response includes empty page",
|
||||
@@ -240,7 +246,7 @@ func TestIterator(t *testing.T) {
|
||||
}
|
||||
it := newRowIterator(context.Background(), nil, pf.fetchPage)
|
||||
it.PageInfo().Token = tc.pageToken
|
||||
values, schema, err := consumeRowIterator(it)
|
||||
values, schema, totalRows, err := consumeRowIterator(it)
|
||||
if err != tc.wantErr {
|
||||
t.Fatalf("%s: got %v, want %v", tc.desc, err, tc.wantErr)
|
||||
}
|
||||
@@ -250,35 +256,31 @@ func TestIterator(t *testing.T) {
|
||||
if (len(schema) != 0 || len(tc.wantSchema) != 0) && !testutil.Equal(schema, tc.wantSchema) {
|
||||
t.Errorf("%s: iterator.Schema:\ngot: %v\nwant: %v", tc.desc, schema, tc.wantSchema)
|
||||
}
|
||||
if totalRows != tc.wantTotalRows {
|
||||
t.Errorf("%s: totalRows: got %d, want %d", tc.desc, totalRows, tc.wantTotalRows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type valueListWithSchema struct {
|
||||
vals valueList
|
||||
schema Schema
|
||||
}
|
||||
|
||||
func (v *valueListWithSchema) Load(vs []Value, s Schema) error {
|
||||
v.vals.Load(vs, s)
|
||||
v.schema = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// consumeRowIterator reads the schema and all values from a RowIterator and returns them.
|
||||
func consumeRowIterator(it *RowIterator) ([][]Value, Schema, error) {
|
||||
var got [][]Value
|
||||
var schema Schema
|
||||
func consumeRowIterator(it *RowIterator) ([][]Value, Schema, uint64, error) {
|
||||
var (
|
||||
got [][]Value
|
||||
schema Schema
|
||||
totalRows uint64
|
||||
)
|
||||
for {
|
||||
var vls valueListWithSchema
|
||||
var vls []Value
|
||||
err := it.Next(&vls)
|
||||
if err == iterator.Done {
|
||||
return got, schema, nil
|
||||
return got, schema, totalRows, nil
|
||||
}
|
||||
if err != nil {
|
||||
return got, schema, err
|
||||
return got, schema, totalRows, err
|
||||
}
|
||||
got = append(got, vls.vals)
|
||||
schema = vls.schema
|
||||
got = append(got, vls)
|
||||
schema = it.Schema
|
||||
totalRows = it.TotalRows
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +335,7 @@ func TestNextAfterFinished(t *testing.T) {
|
||||
}
|
||||
it := newRowIterator(context.Background(), nil, pf.fetchPage)
|
||||
|
||||
values, _, err := consumeRowIterator(it)
|
||||
values, _, _, err := consumeRowIterator(it)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -355,7 +357,7 @@ func TestIteratorNextTypes(t *testing.T) {
|
||||
struct{}{},
|
||||
} {
|
||||
if err := it.Next(v); err == nil {
|
||||
t.Error("%v: want error, got nil", v)
|
||||
t.Errorf("%v: want error, got nil", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -271,7 +271,7 @@ func (j *Job) read(ctx context.Context, waitForQuery func(context.Context, strin
|
||||
}
|
||||
dt := bqToTable(destTable, j.c)
|
||||
it := newRowIterator(ctx, dt, pf)
|
||||
it.schema = schema
|
||||
it.Schema = schema
|
||||
return it, nil
|
||||
}
|
||||
|
||||
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package bigquery
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/civil"
|
||||
)
|
||||
|
||||
// NullInt64 represents a BigQuery INT64 that may be NULL.
|
||||
type NullInt64 struct {
|
||||
Int64 int64
|
||||
Valid bool // Valid is true if Int64 is not NULL.
|
||||
}
|
||||
|
||||
func (n NullInt64) String() string { return nullstr(n.Valid, n.Int64) }
|
||||
|
||||
// NullString represents a BigQuery STRING that may be NULL.
|
||||
type NullString struct {
|
||||
StringVal string
|
||||
Valid bool // Valid is true if StringVal is not NULL.
|
||||
}
|
||||
|
||||
func (n NullString) String() string { return nullstr(n.Valid, n.StringVal) }
|
||||
|
||||
// NullFloat64 represents a BigQuery FLOAT64 that may be NULL.
|
||||
type NullFloat64 struct {
|
||||
Float64 float64
|
||||
Valid bool // Valid is true if Float64 is not NULL.
|
||||
}
|
||||
|
||||
func (n NullFloat64) String() string { return nullstr(n.Valid, n.Float64) }
|
||||
|
||||
// NullBool represents a BigQuery BOOL that may be NULL.
|
||||
type NullBool struct {
|
||||
Bool bool
|
||||
Valid bool // Valid is true if Bool is not NULL.
|
||||
}
|
||||
|
||||
func (n NullBool) String() string { return nullstr(n.Valid, n.Bool) }
|
||||
|
||||
// NullTimestamp represents a BigQuery TIMESTAMP that may be null.
|
||||
type NullTimestamp struct {
|
||||
Timestamp time.Time
|
||||
Valid bool // Valid is true if Time is not NULL.
|
||||
}
|
||||
|
||||
func (n NullTimestamp) String() string { return nullstr(n.Valid, n.Timestamp) }
|
||||
|
||||
// NullDate represents a BigQuery DATE that may be null.
|
||||
type NullDate struct {
|
||||
Date civil.Date
|
||||
Valid bool // Valid is true if Date is not NULL.
|
||||
}
|
||||
|
||||
func (n NullDate) String() string { return nullstr(n.Valid, n.Date) }
|
||||
|
||||
// NullTime represents a BigQuery TIME that may be null.
|
||||
type NullTime struct {
|
||||
Time civil.Time
|
||||
Valid bool // Valid is true if Time is not NULL.
|
||||
}
|
||||
|
||||
func (n NullTime) String() string {
|
||||
if !n.Valid {
|
||||
return "<null>"
|
||||
}
|
||||
return CivilTimeString(n.Time)
|
||||
}
|
||||
|
||||
// NullDateTime represents a BigQuery DATETIME that may be null.
|
||||
type NullDateTime struct {
|
||||
DateTime civil.DateTime
|
||||
Valid bool // Valid is true if DateTime is not NULL.
|
||||
}
|
||||
|
||||
func (n NullDateTime) String() string {
|
||||
if !n.Valid {
|
||||
return "<null>"
|
||||
}
|
||||
return CivilDateTimeString(n.DateTime)
|
||||
}
|
||||
|
||||
func (n NullInt64) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Int64) }
|
||||
func (n NullFloat64) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Float64) }
|
||||
func (n NullBool) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Bool) }
|
||||
func (n NullString) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.StringVal) }
|
||||
func (n NullTimestamp) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Timestamp) }
|
||||
func (n NullDate) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Date) }
|
||||
|
||||
func (n NullTime) MarshalJSON() ([]byte, error) {
|
||||
if !n.Valid {
|
||||
return jsonNull, nil
|
||||
}
|
||||
return []byte(`"` + CivilTimeString(n.Time) + `"`), nil
|
||||
}
|
||||
|
||||
func (n NullDateTime) MarshalJSON() ([]byte, error) {
|
||||
if !n.Valid {
|
||||
return jsonNull, nil
|
||||
}
|
||||
return []byte(`"` + CivilDateTimeString(n.DateTime) + `"`), nil
|
||||
}
|
||||
|
||||
func nullstr(valid bool, v interface{}) string {
|
||||
if !valid {
|
||||
return "NULL"
|
||||
}
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
|
||||
var jsonNull = []byte("null")
|
||||
|
||||
func nulljson(valid bool, v interface{}) ([]byte, error) {
|
||||
if !valid {
|
||||
return jsonNull, nil
|
||||
}
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
var (
|
||||
typeOfNullInt64 = reflect.TypeOf(NullInt64{})
|
||||
typeOfNullFloat64 = reflect.TypeOf(NullFloat64{})
|
||||
typeOfNullBool = reflect.TypeOf(NullBool{})
|
||||
typeOfNullString = reflect.TypeOf(NullString{})
|
||||
typeOfNullTimestamp = reflect.TypeOf(NullTimestamp{})
|
||||
typeOfNullDate = reflect.TypeOf(NullDate{})
|
||||
typeOfNullTime = reflect.TypeOf(NullTime{})
|
||||
typeOfNullDateTime = reflect.TypeOf(NullDateTime{})
|
||||
)
|
||||
|
||||
func nullableFieldType(t reflect.Type) FieldType {
|
||||
switch t {
|
||||
case typeOfNullInt64:
|
||||
return IntegerFieldType
|
||||
case typeOfNullFloat64:
|
||||
return FloatFieldType
|
||||
case typeOfNullBool:
|
||||
return BooleanFieldType
|
||||
case typeOfNullString:
|
||||
return StringFieldType
|
||||
case typeOfNullTimestamp:
|
||||
return TimestampFieldType
|
||||
case typeOfNullDate:
|
||||
return DateFieldType
|
||||
case typeOfNullTime:
|
||||
return TimeFieldType
|
||||
case typeOfNullDateTime:
|
||||
return DateTimeFieldType
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package bigquery
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNullsJSON(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in interface{}
|
||||
want string
|
||||
}{
|
||||
{NullInt64{Valid: true, Int64: 3}, `3`},
|
||||
{NullFloat64{Valid: true, Float64: 3.14}, `3.14`},
|
||||
{NullBool{Valid: true, Bool: true}, `true`},
|
||||
{NullString{Valid: true, StringVal: "foo"}, `"foo"`},
|
||||
{NullTimestamp{Valid: true, Timestamp: testTimestamp}, `"2016-11-05T07:50:22.000000008Z"`},
|
||||
{NullDate{Valid: true, Date: testDate}, `"2016-11-05"`},
|
||||
{NullTime{Valid: true, Time: testTime}, `"07:50:22.000000"`},
|
||||
{NullDateTime{Valid: true, DateTime: testDateTime}, `"2016-11-05 07:50:22.000000"`},
|
||||
|
||||
{NullInt64{}, `null`},
|
||||
{NullFloat64{}, `null`},
|
||||
{NullBool{}, `null`},
|
||||
{NullString{}, `null`},
|
||||
{NullTimestamp{}, `null`},
|
||||
{NullDate{}, `null`},
|
||||
{NullTime{}, `null`},
|
||||
{NullDateTime{}, `null`},
|
||||
} {
|
||||
bytes, err := json.Marshal(test.in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := string(bytes), test.want; got != want {
|
||||
t.Errorf("%#v: got %s, want %s", test.in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-9
@@ -37,17 +37,24 @@ var (
|
||||
validFieldName = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]{0,127}$")
|
||||
)
|
||||
|
||||
const nullableTagOption = "nullable"
|
||||
|
||||
func bqTagParser(t reflect.StructTag) (name string, keep bool, other interface{}, err error) {
|
||||
if s := t.Get("bigquery"); s != "" {
|
||||
if s == "-" {
|
||||
return "", false, nil, nil
|
||||
}
|
||||
if !validFieldName.MatchString(s) {
|
||||
return "", false, nil, errInvalidFieldName
|
||||
}
|
||||
return s, true, nil, nil
|
||||
name, keep, opts, err := fields.ParseStandardTag("bigquery", t)
|
||||
if err != nil {
|
||||
return "", false, nil, err
|
||||
}
|
||||
return "", true, nil, nil
|
||||
if name != "" && !validFieldName.MatchString(name) {
|
||||
return "", false, nil, errInvalidFieldName
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if opt != nullableTagOption {
|
||||
return "", false, nil, fmt.Errorf(
|
||||
"bigquery: invalid tag option %q. The only valid option is %q",
|
||||
opt, nullableTagOption)
|
||||
}
|
||||
}
|
||||
return name, keep, opts, nil
|
||||
}
|
||||
|
||||
var fieldCache = fields.NewCache(bqTagParser, nil, nil)
|
||||
|
||||
+4
-5
@@ -264,19 +264,18 @@ func TestConvertParamValue(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIntegration_ScalarParam(t *testing.T) {
|
||||
timeEqualMicrosec := cmp.Comparer(func(t1, t2 time.Time) bool {
|
||||
return t1.Round(time.Microsecond).Equal(t2.Round(time.Microsecond))
|
||||
})
|
||||
roundToMicros := cmp.Transformer("RoundToMicros",
|
||||
func(t time.Time) time.Time { return t.Round(time.Microsecond) })
|
||||
c := getClient(t)
|
||||
for _, test := range scalarTests {
|
||||
gotData, gotParam, err := paramRoundTrip(c, test.val)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testutil.Equal(gotData, test.val, timeEqualMicrosec) {
|
||||
if !testutil.Equal(gotData, test.val, roundToMicros) {
|
||||
t.Errorf("\ngot %#v (%T)\nwant %#v (%T)", gotData, gotData, test.val, test.val)
|
||||
}
|
||||
if !testutil.Equal(gotParam, test.val, timeEqualMicrosec) {
|
||||
if !testutil.Equal(gotParam, test.val, roundToMicros) {
|
||||
t.Errorf("\ngot %#v (%T)\nwant %#v (%T)", gotParam, gotParam, test.val, test.val)
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -100,6 +100,10 @@ type QueryConfig struct {
|
||||
// It is illegal to mix positional and named syntax.
|
||||
Parameters []QueryParameter
|
||||
|
||||
// TimePartitioning specifies time-based partitioning
|
||||
// for the destination table.
|
||||
TimePartitioning *TimePartitioning
|
||||
|
||||
// The labels associated with this job.
|
||||
Labels map[string]string
|
||||
|
||||
@@ -121,6 +125,7 @@ func (qc *QueryConfig) toBQ() (*bq.JobConfiguration, error) {
|
||||
AllowLargeResults: qc.AllowLargeResults,
|
||||
Priority: string(qc.Priority),
|
||||
MaximumBytesBilled: qc.MaxBytesBilled,
|
||||
TimePartitioning: qc.TimePartitioning.toBQ(),
|
||||
}
|
||||
if len(qc.TableDefinitions) > 0 {
|
||||
qconf.TableDefinitions = make(map[string]bq.ExternalDataConfiguration)
|
||||
@@ -188,6 +193,7 @@ func bqToQueryConfig(q *bq.JobConfiguration, c *Client) (*QueryConfig, error) {
|
||||
MaxBytesBilled: qq.MaximumBytesBilled,
|
||||
UseLegacySQL: qq.UseLegacySql,
|
||||
UseStandardSQL: !qq.UseLegacySql,
|
||||
TimePartitioning: bqToTimePartitioning(qq.TimePartitioning),
|
||||
}
|
||||
if len(qq.TableDefinitions) > 0 {
|
||||
qc.TableDefinitions = make(map[string]ExternalData)
|
||||
|
||||
+5
-2
@@ -16,6 +16,7 @@ package bigquery
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
@@ -350,6 +351,7 @@ func TestConfiguringQuery(t *testing.T) {
|
||||
query.JobID = "ajob"
|
||||
query.DefaultProjectID = "def-project-id"
|
||||
query.DefaultDatasetID = "def-dataset-id"
|
||||
query.TimePartitioning = &TimePartitioning{Expiration: 1234 * time.Second, Field: "f"}
|
||||
// Note: Other configuration fields are tested in other tests above.
|
||||
// A lot of that can be consolidated once Client.Copy is gone.
|
||||
|
||||
@@ -361,8 +363,9 @@ func TestConfiguringQuery(t *testing.T) {
|
||||
ProjectId: "def-project-id",
|
||||
DatasetId: "def-dataset-id",
|
||||
},
|
||||
UseLegacySql: false,
|
||||
ForceSendFields: []string{"UseLegacySql"},
|
||||
UseLegacySql: false,
|
||||
TimePartitioning: &bq.TimePartitioning{ExpirationMs: 1234000, Field: "f", Type: "DAY"},
|
||||
ForceSendFields: []string{"UseLegacySql"},
|
||||
},
|
||||
},
|
||||
JobReference: &bq.JobReference{
|
||||
|
||||
+100
-22
@@ -122,25 +122,71 @@ var (
|
||||
errNoStruct = errors.New("bigquery: can only infer schema from struct or pointer to struct")
|
||||
errUnsupportedFieldType = errors.New("bigquery: unsupported type of field in struct")
|
||||
errInvalidFieldName = errors.New("bigquery: invalid name of field in struct")
|
||||
errBadNullable = errors.New(`bigquery: use "nullable" only for []byte and struct pointers; for all other types, use a NullXXX type`)
|
||||
)
|
||||
|
||||
var typeOfByteSlice = reflect.TypeOf([]byte{})
|
||||
|
||||
// InferSchema tries to derive a BigQuery schema from the supplied struct value.
|
||||
// NOTE: All fields in the returned Schema are configured to be required,
|
||||
// unless the corresponding field in the supplied struct is a slice or array.
|
||||
// Each exported struct field is mapped to a field in the schema.
|
||||
//
|
||||
// It is considered an error if the struct (including nested structs) contains
|
||||
// any exported fields that are pointers or one of the following types:
|
||||
// uint, uint64, uintptr, map, interface, complex64, complex128, func, chan.
|
||||
// In these cases, an error will be returned.
|
||||
// Future versions may handle these cases without error.
|
||||
// The following BigQuery types are inferred from the corresponding Go types.
|
||||
// (This is the same mapping as that used for RowIterator.Next.) Fields inferred
|
||||
// from these types are marked required (non-nullable).
|
||||
//
|
||||
// STRING string
|
||||
// BOOL bool
|
||||
// INTEGER int, int8, int16, int32, int64, uint8, uint16, uint32
|
||||
// FLOAT float32, float64
|
||||
// BYTES []byte
|
||||
// TIMESTAMP time.Time
|
||||
// DATE civil.Date
|
||||
// TIME civil.Time
|
||||
// DATETIME civil.DateTime
|
||||
//
|
||||
// A Go slice or array type is inferred to be a BigQuery repeated field of the
|
||||
// element type. The element type must be one of the above listed types.
|
||||
//
|
||||
// Nullable fields are inferred from the NullXXX types, declared in this package:
|
||||
//
|
||||
// STRING NullString
|
||||
// BOOL NullBool
|
||||
// INTEGER NullInt64
|
||||
// FLOAT NullFloat64
|
||||
// TIMESTAMP NullTimestamp
|
||||
// DATE NullDate
|
||||
// TIME NullTime
|
||||
// DATETIME NullDateTime
|
||||
|
||||
// For a nullable BYTES field, use the type []byte and tag the field "nullable" (see below).
|
||||
//
|
||||
// A struct field that is of struct type is inferred to be a required field of type
|
||||
// RECORD with a schema inferred recursively. For backwards compatibility, a field of
|
||||
// type pointer to struct is also inferred to be required. To get a nullable RECORD
|
||||
// field, use the "nullable" tag (see below).
|
||||
//
|
||||
// InferSchema returns an error if any of the examined fields is of type uint,
|
||||
// uint64, uintptr, map, interface, complex64, complex128, func, or chan. Future
|
||||
// versions may handle these cases without error.
|
||||
//
|
||||
// Recursively defined structs are also disallowed.
|
||||
//
|
||||
// Struct fields may be tagged in a way similar to the encoding/json package.
|
||||
// A tag of the form
|
||||
// bigquery:"name"
|
||||
// uses "name" instead of the struct field name as the BigQuery field name.
|
||||
// A tag of the form
|
||||
// bigquery:"-"
|
||||
// omits the field from the inferred schema.
|
||||
// The "nullable" option marks the field as nullable (not required). It is only
|
||||
// needed for []byte and pointer-to-struct fields, and cannot appear on other
|
||||
// fields. In this example, the Go name of the field is retained:
|
||||
// bigquery:",nullable"
|
||||
func InferSchema(st interface{}) (Schema, error) {
|
||||
return inferSchemaReflectCached(reflect.TypeOf(st))
|
||||
}
|
||||
|
||||
// TODO(jba): replace with sync.Map for Go 1.9.
|
||||
var schemaCache atomiccache.Cache
|
||||
|
||||
type cacheVal struct {
|
||||
@@ -184,10 +230,14 @@ func inferStruct(t reflect.Type) (Schema, error) {
|
||||
}
|
||||
|
||||
// inferFieldSchema infers the FieldSchema for a Go type
|
||||
func inferFieldSchema(rt reflect.Type) (*FieldSchema, error) {
|
||||
func inferFieldSchema(rt reflect.Type, nullable bool) (*FieldSchema, error) {
|
||||
// Only []byte and struct pointers can be tagged nullable.
|
||||
if nullable && !(rt == typeOfByteSlice || rt.Kind() == reflect.Ptr && rt.Elem().Kind() == reflect.Struct) {
|
||||
return nil, errBadNullable
|
||||
}
|
||||
switch rt {
|
||||
case typeOfByteSlice:
|
||||
return &FieldSchema{Required: true, Type: BytesFieldType}, nil
|
||||
return &FieldSchema{Required: !nullable, Type: BytesFieldType}, nil
|
||||
case typeOfGoTime:
|
||||
return &FieldSchema{Required: true, Type: TimestampFieldType}, nil
|
||||
case typeOfDate:
|
||||
@@ -197,7 +247,10 @@ func inferFieldSchema(rt reflect.Type) (*FieldSchema, error) {
|
||||
case typeOfDateTime:
|
||||
return &FieldSchema{Required: true, Type: DateTimeFieldType}, nil
|
||||
}
|
||||
if isSupportedIntType(rt) {
|
||||
if ft := nullableFieldType(rt); ft != "" {
|
||||
return &FieldSchema{Required: false, Type: ft}, nil
|
||||
}
|
||||
if isSupportedIntType(rt) || isSupportedUintType(rt) {
|
||||
return &FieldSchema{Required: true, Type: IntegerFieldType}, nil
|
||||
}
|
||||
switch rt.Kind() {
|
||||
@@ -207,26 +260,34 @@ func inferFieldSchema(rt reflect.Type) (*FieldSchema, error) {
|
||||
// Multi dimensional slices/arrays are not supported by BigQuery
|
||||
return nil, errUnsupportedFieldType
|
||||
}
|
||||
|
||||
f, err := inferFieldSchema(et)
|
||||
if nullableFieldType(et) != "" {
|
||||
// Repeated nullable types are not supported by BigQuery.
|
||||
return nil, errUnsupportedFieldType
|
||||
}
|
||||
f, err := inferFieldSchema(et, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Repeated = true
|
||||
f.Required = false
|
||||
return f, nil
|
||||
case reflect.Struct, reflect.Ptr:
|
||||
case reflect.Ptr:
|
||||
if rt.Elem().Kind() != reflect.Struct {
|
||||
return nil, errUnsupportedFieldType
|
||||
}
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
nested, err := inferStruct(rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FieldSchema{Required: true, Type: RecordFieldType, Schema: nested}, nil
|
||||
return &FieldSchema{Required: !nullable, Type: RecordFieldType, Schema: nested}, nil
|
||||
case reflect.String:
|
||||
return &FieldSchema{Required: true, Type: StringFieldType}, nil
|
||||
return &FieldSchema{Required: !nullable, Type: StringFieldType}, nil
|
||||
case reflect.Bool:
|
||||
return &FieldSchema{Required: true, Type: BooleanFieldType}, nil
|
||||
return &FieldSchema{Required: !nullable, Type: BooleanFieldType}, nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return &FieldSchema{Required: true, Type: FloatFieldType}, nil
|
||||
return &FieldSchema{Required: !nullable, Type: FloatFieldType}, nil
|
||||
default:
|
||||
return nil, errUnsupportedFieldType
|
||||
}
|
||||
@@ -240,7 +301,14 @@ func inferFields(rt reflect.Type) (Schema, error) {
|
||||
return nil, err
|
||||
}
|
||||
for _, field := range fields {
|
||||
f, err := inferFieldSchema(field.Type)
|
||||
var nullable bool
|
||||
for _, opt := range field.ParsedTag.([]string) {
|
||||
if opt == nullableTagOption {
|
||||
nullable = true
|
||||
break
|
||||
}
|
||||
}
|
||||
f, err := inferFieldSchema(field.Type, nullable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -250,12 +318,22 @@ func inferFields(rt reflect.Type) (Schema, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// isSupportedIntType reports whether t can be properly represented by the
|
||||
// BigQuery INTEGER/INT64 type.
|
||||
// isSupportedIntType reports whether t is an int type that can be properly
|
||||
// represented by the BigQuery INTEGER/INT64 type.
|
||||
func isSupportedIntType(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int,
|
||||
reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isSupportedIntType reports whether t is a uint type that can be properly
|
||||
// represented by the BigQuery INTEGER/INT64 type.
|
||||
func isSupportedUintType(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
+111
-11
@@ -248,6 +248,14 @@ func reqField(name, typ string) *FieldSchema {
|
||||
}
|
||||
}
|
||||
|
||||
func optField(name, typ string) *FieldSchema {
|
||||
return &FieldSchema{
|
||||
Name: name,
|
||||
Type: FieldType(typ),
|
||||
Required: false,
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleInference(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in interface{}
|
||||
@@ -491,6 +499,37 @@ func TestRepeatedInference(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type allNulls struct {
|
||||
A NullInt64
|
||||
B NullFloat64
|
||||
C NullBool
|
||||
D NullString
|
||||
E NullTimestamp
|
||||
F NullTime
|
||||
G NullDate
|
||||
H NullDateTime
|
||||
}
|
||||
|
||||
func TestNullInference(t *testing.T) {
|
||||
got, err := InferSchema(allNulls{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := Schema{
|
||||
optField("A", "INTEGER"),
|
||||
optField("B", "FLOAT"),
|
||||
optField("C", "BOOLEAN"),
|
||||
optField("D", "STRING"),
|
||||
optField("E", "TIMESTAMP"),
|
||||
optField("F", "TIME"),
|
||||
optField("G", "DATE"),
|
||||
optField("H", "DATETIME"),
|
||||
}
|
||||
if diff := testutil.Diff(got, want); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
||||
|
||||
type Embedded struct {
|
||||
Embedded int
|
||||
}
|
||||
@@ -532,10 +571,11 @@ func TestRecursiveInference(t *testing.T) {
|
||||
|
||||
type withTags struct {
|
||||
NoTag int
|
||||
ExcludeTag int `bigquery:"-"`
|
||||
SimpleTag int `bigquery:"simple_tag"`
|
||||
UnderscoreTag int `bigquery:"_id"`
|
||||
MixedCase int `bigquery:"MIXEDcase"`
|
||||
ExcludeTag int `bigquery:"-"`
|
||||
SimpleTag int `bigquery:"simple_tag"`
|
||||
UnderscoreTag int `bigquery:"_id"`
|
||||
MixedCase int `bigquery:"MIXEDcase"`
|
||||
Nullable []byte `bigquery:",nullable"`
|
||||
}
|
||||
|
||||
type withTagsNested struct {
|
||||
@@ -544,6 +584,8 @@ type withTagsNested struct {
|
||||
ExcludeTag int `bigquery:"-"`
|
||||
Inside int `bigquery:"inside"`
|
||||
} `bigquery:"anon"`
|
||||
PNested *struct{ X int } // not nullable, for backwards compatibility
|
||||
PNestedNullable *struct{ X int } `bigquery:",nullable"`
|
||||
}
|
||||
|
||||
type withTagsRepeated struct {
|
||||
@@ -563,6 +605,7 @@ var withTagsSchema = Schema{
|
||||
reqField("simple_tag", "INTEGER"),
|
||||
reqField("_id", "INTEGER"),
|
||||
reqField("MIXEDcase", "INTEGER"),
|
||||
optField("Nullable", "BYTES"),
|
||||
}
|
||||
|
||||
func TestTagInference(t *testing.T) {
|
||||
@@ -589,6 +632,18 @@ func TestTagInference(t *testing.T) {
|
||||
Type: "RECORD",
|
||||
Schema: Schema{reqField("inside", "INTEGER")},
|
||||
},
|
||||
&FieldSchema{
|
||||
Name: "PNested",
|
||||
Required: true,
|
||||
Type: "RECORD",
|
||||
Schema: Schema{reqField("X", "INTEGER")},
|
||||
},
|
||||
&FieldSchema{
|
||||
Name: "PNestedNullable",
|
||||
Required: false,
|
||||
Type: "RECORD",
|
||||
Schema: Schema{reqField("X", "INTEGER")},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -666,12 +721,6 @@ func TestTagInferenceErrors(t *testing.T) {
|
||||
}{},
|
||||
err: errInvalidFieldName,
|
||||
},
|
||||
{
|
||||
in: struct {
|
||||
OmitEmpty int `bigquery:"abc,omitempty"`
|
||||
}{},
|
||||
err: errInvalidFieldName,
|
||||
},
|
||||
}
|
||||
for i, tc := range testCases {
|
||||
want := tc.err
|
||||
@@ -680,6 +729,13 @@ func TestTagInferenceErrors(t *testing.T) {
|
||||
t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant:\n%#v", i, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := InferSchema(struct {
|
||||
X int `bigquery:",optional"`
|
||||
}{})
|
||||
if err == nil {
|
||||
t.Error("got nil, want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchemaErrors(t *testing.T) {
|
||||
@@ -721,7 +777,7 @@ func TestSchemaErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
in: struct{ Ptr *int }{},
|
||||
err: errNoStruct,
|
||||
err: errUnsupportedFieldType,
|
||||
},
|
||||
{
|
||||
in: struct{ Interface interface{} }{},
|
||||
@@ -735,6 +791,14 @@ func TestSchemaErrors(t *testing.T) {
|
||||
in: struct{ MultiDimensional [][][]byte }{},
|
||||
err: errUnsupportedFieldType,
|
||||
},
|
||||
{
|
||||
in: struct{ SliceOfPointer []*int }{},
|
||||
err: errUnsupportedFieldType,
|
||||
},
|
||||
{
|
||||
in: struct{ SliceOfNull []NullInt64 }{},
|
||||
err: errUnsupportedFieldType,
|
||||
},
|
||||
{
|
||||
in: struct{ ChanSlice []chan bool }{},
|
||||
err: errUnsupportedFieldType,
|
||||
@@ -743,6 +807,42 @@ func TestSchemaErrors(t *testing.T) {
|
||||
in: struct{ NestedChan struct{ Chan []chan bool } }{},
|
||||
err: errUnsupportedFieldType,
|
||||
},
|
||||
{
|
||||
in: struct {
|
||||
X int `bigquery:",nullable"`
|
||||
}{},
|
||||
err: errBadNullable,
|
||||
},
|
||||
{
|
||||
in: struct {
|
||||
X bool `bigquery:",nullable"`
|
||||
}{},
|
||||
err: errBadNullable,
|
||||
},
|
||||
{
|
||||
in: struct {
|
||||
X struct{ N int } `bigquery:",nullable"`
|
||||
}{},
|
||||
err: errBadNullable,
|
||||
},
|
||||
{
|
||||
in: struct {
|
||||
X []int `bigquery:",nullable"`
|
||||
}{},
|
||||
err: errBadNullable,
|
||||
},
|
||||
{
|
||||
in: struct{ X *[]byte }{},
|
||||
err: errUnsupportedFieldType,
|
||||
},
|
||||
{
|
||||
in: struct{ X *[]int }{},
|
||||
err: errUnsupportedFieldType,
|
||||
},
|
||||
{
|
||||
in: struct{ X *int }{},
|
||||
err: errUnsupportedFieldType,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
want := tc.err
|
||||
|
||||
+7
@@ -147,6 +147,11 @@ type TimePartitioning struct {
|
||||
// The amount of time to keep the storage for a partition.
|
||||
// If the duration is empty (0), the data in the partitions do not expire.
|
||||
Expiration time.Duration
|
||||
|
||||
// If empty, the table is partitioned by pseudo column '_PARTITIONTIME'; if set, the
|
||||
// table is partitioned by this field. The field must be a top-level TIMESTAMP or
|
||||
// DATE field. Its mode must be NULLABLE or REQUIRED.
|
||||
Field string
|
||||
}
|
||||
|
||||
func (p *TimePartitioning) toBQ() *bq.TimePartitioning {
|
||||
@@ -156,6 +161,7 @@ func (p *TimePartitioning) toBQ() *bq.TimePartitioning {
|
||||
return &bq.TimePartitioning{
|
||||
Type: "DAY",
|
||||
ExpirationMs: int64(p.Expiration / time.Millisecond),
|
||||
Field: p.Field,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +171,7 @@ func bqToTimePartitioning(q *bq.TimePartitioning) *TimePartitioning {
|
||||
}
|
||||
return &TimePartitioning{
|
||||
Expiration: time.Duration(q.ExpirationMs) * time.Millisecond,
|
||||
Field: q.Field,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-4
@@ -51,6 +51,7 @@ func TestBQToTableMetadata(t *testing.T) {
|
||||
TimePartitioning: &bq.TimePartitioning{
|
||||
ExpirationMs: 7890,
|
||||
Type: "DAY",
|
||||
Field: "pfield",
|
||||
},
|
||||
Type: "EXTERNAL",
|
||||
View: &bq.ViewDefinition{Query: "view-query"},
|
||||
@@ -72,7 +73,10 @@ func TestBQToTableMetadata(t *testing.T) {
|
||||
LastModifiedTime: aTime.Truncate(time.Millisecond),
|
||||
NumBytes: 123,
|
||||
NumRows: 7,
|
||||
TimePartitioning: &TimePartitioning{Expiration: 7890 * time.Millisecond},
|
||||
TimePartitioning: &TimePartitioning{
|
||||
Expiration: 7890 * time.Millisecond,
|
||||
Field: "pfield",
|
||||
},
|
||||
StreamingBuffer: &StreamingBuffer{
|
||||
EstimatedBytes: 11,
|
||||
EstimatedRows: 3,
|
||||
@@ -154,9 +158,12 @@ func TestTableMetadataToBQ(t *testing.T) {
|
||||
},
|
||||
{
|
||||
&TableMetadata{
|
||||
ViewQuery: "q",
|
||||
UseStandardSQL: true,
|
||||
TimePartitioning: &TimePartitioning{time.Second},
|
||||
ViewQuery: "q",
|
||||
UseStandardSQL: true,
|
||||
TimePartitioning: &TimePartitioning{
|
||||
Expiration: time.Second,
|
||||
Field: "ofDreams",
|
||||
},
|
||||
},
|
||||
&bq.Table{
|
||||
View: &bq.ViewDefinition{
|
||||
@@ -167,6 +174,7 @@ func TestTableMetadataToBQ(t *testing.T) {
|
||||
TimePartitioning: &bq.TimePartitioning{
|
||||
Type: "DAY",
|
||||
ExpirationMs: 1000,
|
||||
Field: "ofDreams",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+9
@@ -54,6 +54,9 @@ type Uploader struct {
|
||||
|
||||
// Uploader returns an Uploader that can be used to append rows to t.
|
||||
// The returned Uploader may optionally be further configured before its Put method is called.
|
||||
//
|
||||
// To stream rows into a date-partitioned table at a particular date, add the
|
||||
// $yyyymmdd suffix to the table name when constructing the Table.
|
||||
func (t *Table) Uploader() *Uploader {
|
||||
return &Uploader{t: t}
|
||||
}
|
||||
@@ -156,6 +159,9 @@ func (u *Uploader) putMulti(ctx context.Context, src []ValueSaver) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
call := u.t.c.bqs.Tabledata.InsertAll(u.t.ProjectID, u.t.DatasetID, u.t.TableID, req)
|
||||
call = call.Context(ctx)
|
||||
setClientHeader(call.Header())
|
||||
@@ -171,6 +177,9 @@ func (u *Uploader) putMulti(ctx context.Context, src []ValueSaver) error {
|
||||
}
|
||||
|
||||
func (u *Uploader) newInsertRequest(savers []ValueSaver) (*bq.TableDataInsertAllRequest, error) {
|
||||
if savers == nil { // If there are no rows, do nothing.
|
||||
return nil, nil
|
||||
}
|
||||
req := &bq.TableDataInsertAllRequest{
|
||||
TemplateSuffix: u.TableTemplateSuffix,
|
||||
IgnoreUnknownValues: u.IgnoreUnknownValues,
|
||||
|
||||
+7
-3
@@ -48,8 +48,9 @@ func TestNewInsertRequest(t *testing.T) {
|
||||
req *bq.TableDataInsertAllRequest
|
||||
}{
|
||||
{
|
||||
ul: &Uploader{},
|
||||
req: &bq.TableDataInsertAllRequest{},
|
||||
ul: &Uploader{},
|
||||
savers: nil,
|
||||
req: nil,
|
||||
},
|
||||
{
|
||||
ul: &Uploader{},
|
||||
@@ -92,7 +93,7 @@ func TestNewInsertRequest(t *testing.T) {
|
||||
}
|
||||
want := tc.req
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Errorf("%#d: %#v: got %#v, want %#v", i, tc.ul, got, want)
|
||||
t.Errorf("%d: %#v: got %#v, want %#v", i, tc.ul, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,6 +156,8 @@ func TestValueSavers(t *testing.T) {
|
||||
in interface{}
|
||||
want []ValueSaver
|
||||
}{
|
||||
{[]interface{}(nil), nil},
|
||||
{[]interface{}{}, nil},
|
||||
{ts, []ValueSaver{ts}},
|
||||
{T{I: 1}, []ValueSaver{&StructSaver{Schema: schema, Struct: T{I: 1}}}},
|
||||
{[]ValueSaver{ts, ts}, []ValueSaver{ts, ts}},
|
||||
@@ -191,6 +194,7 @@ func TestValueSavers(t *testing.T) {
|
||||
|
||||
func TestValueSaversErrors(t *testing.T) {
|
||||
inputs := []interface{}{
|
||||
nil,
|
||||
1,
|
||||
[]int{1, 2},
|
||||
[]interface{}{
|
||||
|
||||
+112
-7
@@ -18,6 +18,7 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -63,6 +64,8 @@ func loadMap(m map[string]Value, vals []Value, s Schema) {
|
||||
val := vals[i]
|
||||
var v interface{}
|
||||
switch {
|
||||
case val == nil:
|
||||
v = val
|
||||
case f.Schema == nil:
|
||||
v = val
|
||||
case !f.Repeated:
|
||||
@@ -79,6 +82,7 @@ func loadMap(m map[string]Value, vals []Value, s Schema) {
|
||||
}
|
||||
v = vs
|
||||
}
|
||||
|
||||
m[f.Name] = v
|
||||
}
|
||||
}
|
||||
@@ -125,6 +129,18 @@ func setInt(v reflect.Value, x interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setUint(v reflect.Value, x interface{}) error {
|
||||
if x == nil {
|
||||
return errNoNulls
|
||||
}
|
||||
xx := x.(int64)
|
||||
if xx < 0 || v.OverflowUint(uint64(xx)) {
|
||||
return fmt.Errorf("bigquery: value %v overflows struct field of type %v", xx, v.Type())
|
||||
}
|
||||
v.SetUint(uint64(xx))
|
||||
return nil
|
||||
}
|
||||
|
||||
func setFloat(v reflect.Value, x interface{}) error {
|
||||
if x == nil {
|
||||
return errNoNulls
|
||||
@@ -155,9 +171,20 @@ func setString(v reflect.Value, x interface{}) error {
|
||||
|
||||
func setBytes(v reflect.Value, x interface{}) error {
|
||||
if x == nil {
|
||||
return errNoNulls
|
||||
v.SetBytes(nil)
|
||||
} else {
|
||||
v.SetBytes(x.([]byte))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setNull(v reflect.Value, x interface{}, build func() interface{}) error {
|
||||
if x == nil {
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
} else {
|
||||
n := build()
|
||||
v.Set(reflect.ValueOf(n))
|
||||
}
|
||||
v.SetBytes(x.([]byte))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -228,7 +255,7 @@ func compileToOps(structType reflect.Type, schema Schema) ([]structLoaderOp, err
|
||||
return nil, err
|
||||
}
|
||||
op.setFunc = func(v reflect.Value, val interface{}) error {
|
||||
return setNested(nested, v, val.([]Value))
|
||||
return setNested(nested, v, val)
|
||||
}
|
||||
} else {
|
||||
op.setFunc = determineSetFunc(t, schemaField.Type)
|
||||
@@ -253,6 +280,13 @@ func determineSetFunc(ftype reflect.Type, stype FieldType) setFunc {
|
||||
if ftype.Kind() == reflect.String {
|
||||
return setString
|
||||
}
|
||||
if ftype == typeOfNullString {
|
||||
return func(v reflect.Value, x interface{}) error {
|
||||
return setNull(v, x, func() interface{} {
|
||||
return NullString{StringVal: x.(string), Valid: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case BytesFieldType:
|
||||
if ftype == typeOfByteSlice {
|
||||
@@ -260,40 +294,91 @@ func determineSetFunc(ftype reflect.Type, stype FieldType) setFunc {
|
||||
}
|
||||
|
||||
case IntegerFieldType:
|
||||
if isSupportedIntType(ftype) {
|
||||
if isSupportedUintType(ftype) {
|
||||
return setUint
|
||||
} else if isSupportedIntType(ftype) {
|
||||
return setInt
|
||||
}
|
||||
if ftype == typeOfNullInt64 {
|
||||
return func(v reflect.Value, x interface{}) error {
|
||||
return setNull(v, x, func() interface{} {
|
||||
return NullInt64{Int64: x.(int64), Valid: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case FloatFieldType:
|
||||
switch ftype.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return setFloat
|
||||
}
|
||||
if ftype == typeOfNullFloat64 {
|
||||
return func(v reflect.Value, x interface{}) error {
|
||||
return setNull(v, x, func() interface{} {
|
||||
return NullFloat64{Float64: x.(float64), Valid: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case BooleanFieldType:
|
||||
if ftype.Kind() == reflect.Bool {
|
||||
return setBool
|
||||
}
|
||||
if ftype == typeOfNullBool {
|
||||
return func(v reflect.Value, x interface{}) error {
|
||||
return setNull(v, x, func() interface{} {
|
||||
return NullBool{Bool: x.(bool), Valid: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case TimestampFieldType:
|
||||
if ftype == typeOfGoTime {
|
||||
return setAny
|
||||
}
|
||||
if ftype == typeOfNullTimestamp {
|
||||
return func(v reflect.Value, x interface{}) error {
|
||||
return setNull(v, x, func() interface{} {
|
||||
return NullTimestamp{Timestamp: x.(time.Time), Valid: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case DateFieldType:
|
||||
if ftype == typeOfDate {
|
||||
return setAny
|
||||
}
|
||||
if ftype == typeOfNullDate {
|
||||
return func(v reflect.Value, x interface{}) error {
|
||||
return setNull(v, x, func() interface{} {
|
||||
return NullDate{Date: x.(civil.Date), Valid: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case TimeFieldType:
|
||||
if ftype == typeOfTime {
|
||||
return setAny
|
||||
}
|
||||
if ftype == typeOfNullTime {
|
||||
return func(v reflect.Value, x interface{}) error {
|
||||
return setNull(v, x, func() interface{} {
|
||||
return NullTime{Time: x.(civil.Time), Valid: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case DateTimeFieldType:
|
||||
if ftype == typeOfDateTime {
|
||||
return setAny
|
||||
}
|
||||
if ftype == typeOfNullDateTime {
|
||||
return func(v reflect.Value, x interface{}) error {
|
||||
return setNull(v, x, func() interface{} {
|
||||
return NullDateTime{DateTime: x.(civil.DateTime), Valid: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -323,16 +408,21 @@ func runOps(ops []structLoaderOp, vstruct reflect.Value, values []Value) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setNested(ops []structLoaderOp, v reflect.Value, vals []Value) error {
|
||||
func setNested(ops []structLoaderOp, v reflect.Value, val interface{}) error {
|
||||
// v is either a struct or a pointer to a struct.
|
||||
if v.Kind() == reflect.Ptr {
|
||||
// If the value is nil, set the pointer to nil.
|
||||
if val == nil {
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
return nil
|
||||
}
|
||||
// If the pointer is nil, set it to a zero struct value.
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
return runOps(ops, v, vals)
|
||||
return runOps(ops, v, val.([]Value))
|
||||
}
|
||||
|
||||
func setRepeated(field reflect.Value, vslice []Value, setElem setFunc) error {
|
||||
@@ -404,6 +494,10 @@ func valuesToMap(vs []Value, schema Schema) (map[string]Value, error) {
|
||||
|
||||
m := make(map[string]Value)
|
||||
for i, fieldSchema := range schema {
|
||||
if vs[i] == nil {
|
||||
m[fieldSchema.Name] = nil
|
||||
continue
|
||||
}
|
||||
if fieldSchema.Type != RecordFieldType {
|
||||
m[fieldSchema.Name] = toUploadValue(vs[i], fieldSchema)
|
||||
continue
|
||||
@@ -550,10 +644,16 @@ func toUploadValue(val interface{}, fs *FieldSchema) interface{} {
|
||||
func toUploadValueReflect(v reflect.Value, fs *FieldSchema) interface{} {
|
||||
switch fs.Type {
|
||||
case TimeFieldType:
|
||||
if v.Type() == typeOfNullTime {
|
||||
return v.Interface()
|
||||
}
|
||||
return civilToUploadValue(v, fs, func(v reflect.Value) string {
|
||||
return CivilTimeString(v.Interface().(civil.Time))
|
||||
})
|
||||
case DateTimeFieldType:
|
||||
if v.Type() == typeOfNullDateTime {
|
||||
return v.Interface()
|
||||
}
|
||||
return civilToUploadValue(v, fs, func(v reflect.Value) string {
|
||||
return CivilDateTimeString(v.Interface().(civil.DateTime))
|
||||
})
|
||||
@@ -705,7 +805,12 @@ func convertBasicType(val string, typ FieldType) (Value, error) {
|
||||
return strconv.ParseBool(val)
|
||||
case TimestampFieldType:
|
||||
f, err := strconv.ParseFloat(val, 64)
|
||||
return Value(time.Unix(0, int64(f*1e9)).UTC()), err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secs := math.Trunc(f)
|
||||
nanos := (f - secs) * 1e9
|
||||
return Value(time.Unix(int64(secs), int64(nanos)).UTC()), nil
|
||||
case DateFieldType:
|
||||
return civil.ParseDate(val)
|
||||
case TimeFieldType:
|
||||
|
||||
+162
-63
@@ -24,7 +24,6 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"cloud.google.com/go/civil"
|
||||
"cloud.google.com/go/internal/pretty"
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
|
||||
bq "google.golang.org/api/bigquery/v2"
|
||||
@@ -89,6 +88,20 @@ func TestConvertTime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertSmallTimes(t *testing.T) {
|
||||
for _, year := range []int{1600, 1066, 1} {
|
||||
want := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
s := fmt.Sprintf("%.10f", float64(want.Unix()))
|
||||
got, err := convertBasicType(s, TimestampFieldType)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !got.(time.Time).Equal(want) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertNullValues(t *testing.T) {
|
||||
schema := []*FieldSchema{
|
||||
{Type: StringFieldType},
|
||||
@@ -512,6 +525,7 @@ func TestStructSaver(t *testing.T) {
|
||||
{Name: "rnested", Type: RecordFieldType, Repeated: true, Schema: Schema{
|
||||
{Name: "b", Type: BooleanFieldType},
|
||||
}},
|
||||
{Name: "p", Type: IntegerFieldType, Required: false},
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -523,6 +537,7 @@ func TestStructSaver(t *testing.T) {
|
||||
TR []civil.Time
|
||||
Nested *N
|
||||
Rnested []*N
|
||||
P NullInt64
|
||||
}
|
||||
)
|
||||
|
||||
@@ -539,10 +554,11 @@ func TestStructSaver(t *testing.T) {
|
||||
if wantIID := "iid"; gotIID != wantIID {
|
||||
t.Errorf("%s: InsertID: got %q, want %q", msg, gotIID, wantIID)
|
||||
}
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Errorf("%s:\ngot\n%#v\nwant\n%#v", msg, got, want)
|
||||
if diff := testutil.Diff(got, want); diff != "" {
|
||||
t.Errorf("%s: %s", msg, diff)
|
||||
}
|
||||
}
|
||||
|
||||
ct1 := civil.Time{1, 2, 3, 4000}
|
||||
ct2 := civil.Time{5, 6, 7, 8000}
|
||||
in := T{
|
||||
@@ -552,6 +568,7 @@ func TestStructSaver(t *testing.T) {
|
||||
TR: []civil.Time{ct1, ct2},
|
||||
Nested: &N{B: true},
|
||||
Rnested: []*N{{true}, {false}},
|
||||
P: NullInt64{Valid: true, Int64: 17},
|
||||
}
|
||||
want := map[string]Value{
|
||||
"s": "x",
|
||||
@@ -560,10 +577,11 @@ func TestStructSaver(t *testing.T) {
|
||||
"tr": []string{"01:02:03.000004", "05:06:07.000008"},
|
||||
"nested": map[string]Value{"b": true},
|
||||
"rnested": []Value{map[string]Value{"b": true}, map[string]Value{"b": false}},
|
||||
"p": NullInt64{Valid: true, Int64: 17},
|
||||
}
|
||||
check("all values", in, want)
|
||||
check("all values, ptr", &in, want)
|
||||
check("empty struct", T{}, map[string]Value{"s": "", "t": "00:00:00"})
|
||||
check("empty struct", T{}, map[string]Value{"s": "", "t": "00:00:00", "p": NullInt64{}})
|
||||
|
||||
// Missing and extra fields ignored.
|
||||
type T2 struct {
|
||||
@@ -577,6 +595,7 @@ func TestStructSaver(t *testing.T) {
|
||||
map[string]Value{
|
||||
"s": "",
|
||||
"t": "00:00:00",
|
||||
"p": NullInt64{},
|
||||
"rnested": []Value{map[string]Value{"b": true}, map[string]Value(nil), map[string]Value{"b": false}},
|
||||
})
|
||||
}
|
||||
@@ -713,6 +732,22 @@ func TestValueMap(t *testing.T) {
|
||||
t.Errorf("got\n%+v\nwant\n%+v", vm, want)
|
||||
}
|
||||
|
||||
in = make([]Value, len(schema))
|
||||
want = map[string]Value{
|
||||
"s": nil,
|
||||
"i": nil,
|
||||
"f": nil,
|
||||
"b": nil,
|
||||
"n": nil,
|
||||
"rn": nil,
|
||||
}
|
||||
var vm2 valueMap
|
||||
if err := vm2.Load(in, schema); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testutil.Equal(vm2, valueMap(want)) {
|
||||
t.Errorf("got\n%+v\nwant\n%+v", vm2, want)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -722,6 +757,7 @@ var (
|
||||
{Name: "s2", Type: StringFieldType},
|
||||
{Name: "by", Type: BytesFieldType},
|
||||
{Name: "I", Type: IntegerFieldType},
|
||||
{Name: "U", Type: IntegerFieldType},
|
||||
{Name: "F", Type: FloatFieldType},
|
||||
{Name: "B", Type: BooleanFieldType},
|
||||
{Name: "TS", Type: TimestampFieldType},
|
||||
@@ -740,7 +776,7 @@ var (
|
||||
testTime = civil.Time{7, 50, 22, 8}
|
||||
testDateTime = civil.DateTime{testDate, testTime}
|
||||
|
||||
testValues = []Value{"x", "y", []byte{1, 2, 3}, int64(7), 3.14, true,
|
||||
testValues = []Value{"x", "y", []byte{1, 2, 3}, int64(7), int64(8), 3.14, true,
|
||||
testTimestamp, testDate, testTime, testDateTime,
|
||||
[]Value{"nested", int64(17)}, "z"}
|
||||
)
|
||||
@@ -748,6 +784,7 @@ var (
|
||||
type testStruct1 struct {
|
||||
B bool
|
||||
I int
|
||||
U uint16
|
||||
times
|
||||
S string
|
||||
S2 String
|
||||
@@ -774,14 +811,13 @@ type times struct {
|
||||
|
||||
func TestStructLoader(t *testing.T) {
|
||||
var ts1 testStruct1
|
||||
if err := load(&ts1, schema2, testValues); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mustLoad(t, &ts1, schema2, testValues)
|
||||
// Note: the schema field named "s" gets matched to the exported struct
|
||||
// field "S", not the unexported "s".
|
||||
want := &testStruct1{
|
||||
B: true,
|
||||
I: 7,
|
||||
U: 8,
|
||||
F: 3.14,
|
||||
times: times{TS: testTimestamp, T: testTime, D: testDate, DT: testDateTime},
|
||||
S: "x",
|
||||
@@ -790,33 +826,25 @@ func TestStructLoader(t *testing.T) {
|
||||
Nested: nested{NestS: "nested", NestI: 17},
|
||||
Tagged: "z",
|
||||
}
|
||||
if !testutil.Equal(&ts1, want, cmp.AllowUnexported(testStruct1{})) {
|
||||
t.Errorf("got %+v, want %+v", pretty.Value(ts1), pretty.Value(*want))
|
||||
d, _, err := pretty.Diff(*want, ts1)
|
||||
if err == nil {
|
||||
t.Logf("diff:\n%s", d)
|
||||
}
|
||||
if diff := testutil.Diff(&ts1, want, cmp.AllowUnexported(testStruct1{})); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
|
||||
// Test pointers to nested structs.
|
||||
type nestedPtr struct{ Nested *nested }
|
||||
var np nestedPtr
|
||||
if err := load(&np, schema2, testValues); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mustLoad(t, &np, schema2, testValues)
|
||||
want2 := &nestedPtr{Nested: &nested{NestS: "nested", NestI: 17}}
|
||||
if !testutil.Equal(&np, want2) {
|
||||
t.Errorf("got %+v, want %+v", pretty.Value(np), pretty.Value(*want2))
|
||||
if diff := testutil.Diff(&np, want2); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
|
||||
// Existing values should be reused.
|
||||
nst := &nested{NestS: "x", NestI: -10}
|
||||
np = nestedPtr{Nested: nst}
|
||||
if err := load(&np, schema2, testValues); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testutil.Equal(&np, want2) {
|
||||
t.Errorf("got %+v, want %+v", pretty.Value(np), pretty.Value(*want2))
|
||||
mustLoad(t, &np, schema2, testValues)
|
||||
if diff := testutil.Diff(&np, want2); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
if np.Nested != nst {
|
||||
t.Error("nested struct pointers not equal")
|
||||
@@ -851,28 +879,23 @@ var (
|
||||
|
||||
func TestStructLoaderRepeated(t *testing.T) {
|
||||
var r1 repStruct
|
||||
if err := load(&r1, repSchema, repValues); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mustLoad(t, &r1, repSchema, repValues)
|
||||
want := repStruct{
|
||||
Nums: []int{1, 2, 3},
|
||||
ShortNums: [...]int{1, 2}, // extra values discarded
|
||||
LongNums: [...]int{1, 2, 3, 0, 0},
|
||||
Nested: []*nested{{"x", 1}, {"y", 2}},
|
||||
}
|
||||
if !testutil.Equal(r1, want) {
|
||||
t.Errorf("got %+v, want %+v", pretty.Value(r1), pretty.Value(want))
|
||||
if diff := testutil.Diff(r1, want); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
|
||||
r2 := repStruct{
|
||||
Nums: []int{-1, -2, -3, -4, -5}, // truncated to zero and appended to
|
||||
LongNums: [...]int{-1, -2, -3, -4, -5}, // unset elements are zeroed
|
||||
}
|
||||
if err := load(&r2, repSchema, repValues); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testutil.Equal(r2, want) {
|
||||
t.Errorf("got %+v, want %+v", pretty.Value(r2), pretty.Value(want))
|
||||
mustLoad(t, &r2, repSchema, repValues)
|
||||
if diff := testutil.Diff(r2, want); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
if got, want := cap(r2.Nums), 5; got != want {
|
||||
t.Errorf("cap(r2.Nums) = %d, want %d", got, want)
|
||||
@@ -880,33 +903,109 @@ func TestStructLoaderRepeated(t *testing.T) {
|
||||
|
||||
// Short slice case.
|
||||
r3 := repStruct{Nums: []int{-1}}
|
||||
if err := load(&r3, repSchema, repValues); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testutil.Equal(r3, want) {
|
||||
t.Errorf("got %+v, want %+v", pretty.Value(r3), pretty.Value(want))
|
||||
mustLoad(t, &r3, repSchema, repValues)
|
||||
if diff := testutil.Diff(r3, want); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
if got, want := cap(r3.Nums), 3; got != want {
|
||||
t.Errorf("cap(r3.Nums) = %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type testStructNullable struct {
|
||||
String NullString
|
||||
Bytes []byte
|
||||
Integer NullInt64
|
||||
Float NullFloat64
|
||||
Boolean NullBool
|
||||
Timestamp NullTimestamp
|
||||
Date NullDate
|
||||
Time NullTime
|
||||
DateTime NullDateTime
|
||||
Record *subNullable
|
||||
}
|
||||
|
||||
type subNullable struct {
|
||||
X NullInt64
|
||||
}
|
||||
|
||||
var testStructNullableSchema = Schema{
|
||||
{Name: "String", Type: StringFieldType, Required: false},
|
||||
{Name: "Bytes", Type: BytesFieldType, Required: false},
|
||||
{Name: "Integer", Type: IntegerFieldType, Required: false},
|
||||
{Name: "Float", Type: FloatFieldType, Required: false},
|
||||
{Name: "Boolean", Type: BooleanFieldType, Required: false},
|
||||
{Name: "Timestamp", Type: TimestampFieldType, Required: false},
|
||||
{Name: "Date", Type: DateFieldType, Required: false},
|
||||
{Name: "Time", Type: TimeFieldType, Required: false},
|
||||
{Name: "DateTime", Type: DateTimeFieldType, Required: false},
|
||||
{Name: "Record", Type: RecordFieldType, Required: false, Schema: Schema{
|
||||
{Name: "X", Type: IntegerFieldType, Required: false},
|
||||
}},
|
||||
}
|
||||
|
||||
func TestStructLoaderNullable(t *testing.T) {
|
||||
var ts testStructNullable
|
||||
nilVals := []Value{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}
|
||||
mustLoad(t, &ts, testStructNullableSchema, nilVals)
|
||||
want := testStructNullable{}
|
||||
if diff := testutil.Diff(ts, want); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
|
||||
nonnilVals := []Value{"x", []byte{1, 2, 3}, int64(1), 2.3, true, testTimestamp, testDate, testTime, testDateTime, []Value{int64(4)}}
|
||||
|
||||
// All ts fields are nil. Loading non-nil values will cause them all to
|
||||
// be allocated.
|
||||
mustLoad(t, &ts, testStructNullableSchema, nonnilVals)
|
||||
want = testStructNullable{
|
||||
String: NullString{StringVal: "x", Valid: true},
|
||||
Bytes: []byte{1, 2, 3},
|
||||
Integer: NullInt64{Int64: 1, Valid: true},
|
||||
Float: NullFloat64{Float64: 2.3, Valid: true},
|
||||
Boolean: NullBool{Bool: true, Valid: true},
|
||||
Timestamp: NullTimestamp{Timestamp: testTimestamp, Valid: true},
|
||||
Date: NullDate{Date: testDate, Valid: true},
|
||||
Time: NullTime{Time: testTime, Valid: true},
|
||||
DateTime: NullDateTime{DateTime: testDateTime, Valid: true},
|
||||
Record: &subNullable{X: NullInt64{Int64: 4, Valid: true}},
|
||||
}
|
||||
if diff := testutil.Diff(ts, want); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
|
||||
// Struct pointers are reused, byte slices are not.
|
||||
want = ts
|
||||
want.Bytes = []byte{17}
|
||||
vals2 := []Value{nil, []byte{17}, nil, nil, nil, nil, nil, nil, nil, []Value{int64(7)}}
|
||||
mustLoad(t, &ts, testStructNullableSchema, vals2)
|
||||
if ts.Record != want.Record {
|
||||
t.Error("record pointers not identical")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructLoaderOverflow(t *testing.T) {
|
||||
type S struct {
|
||||
I int16
|
||||
U uint16
|
||||
F float32
|
||||
}
|
||||
schema := Schema{
|
||||
{Name: "I", Type: IntegerFieldType},
|
||||
{Name: "U", Type: IntegerFieldType},
|
||||
{Name: "F", Type: FloatFieldType},
|
||||
}
|
||||
var s S
|
||||
if err := load(&s, schema, []Value{int64(math.MaxInt16 + 1), 0}); err == nil {
|
||||
t.Error("int: got nil, want error")
|
||||
}
|
||||
if err := load(&s, schema, []Value{int64(0), math.MaxFloat32 * 2}); err == nil {
|
||||
t.Error("float: got nil, want error")
|
||||
z64 := int64(0)
|
||||
for _, vals := range [][]Value{
|
||||
{int64(math.MaxInt16 + 1), z64, 0},
|
||||
{z64, int64(math.MaxInt32), 0},
|
||||
{z64, int64(-1), 0},
|
||||
{z64, z64, math.MaxFloat32 * 2},
|
||||
} {
|
||||
if err := load(&s, schema, vals); err == nil {
|
||||
t.Errorf("%+v: got nil, want error", vals)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -922,20 +1021,18 @@ func TestStructLoaderFieldOverlap(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want1 := S1{I: 7}
|
||||
if !testutil.Equal(s1, want1) {
|
||||
t.Errorf("got %+v, want %+v", pretty.Value(s1), pretty.Value(want1))
|
||||
if diff := testutil.Diff(s1, want1); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
|
||||
// It's even valid to have no overlapping fields at all.
|
||||
type S2 struct{ Z int }
|
||||
|
||||
var s2 S2
|
||||
if err := load(&s2, schema2, testValues); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mustLoad(t, &s2, schema2, testValues)
|
||||
want2 := S2{}
|
||||
if !testutil.Equal(s2, want2) {
|
||||
t.Errorf("got %+v, want %+v", pretty.Value(s2), pretty.Value(want2))
|
||||
if diff := testutil.Diff(s2, want2); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -989,21 +1086,17 @@ func TestStructLoaderErrors(t *testing.T) {
|
||||
{Name: "f", Type: FloatFieldType},
|
||||
{Name: "b", Type: BooleanFieldType},
|
||||
{Name: "s", Type: StringFieldType},
|
||||
{Name: "by", Type: BytesFieldType},
|
||||
{Name: "d", Type: DateFieldType},
|
||||
}
|
||||
type s struct {
|
||||
I int
|
||||
F float64
|
||||
B bool
|
||||
S string
|
||||
By []byte
|
||||
D civil.Date
|
||||
}
|
||||
vals := []Value{int64(0), 0.0, false, "", []byte{}, testDate}
|
||||
if err := load(&s{}, schema, vals); err != nil {
|
||||
t.Fatal(err)
|
||||
I int
|
||||
F float64
|
||||
B bool
|
||||
S string
|
||||
D civil.Date
|
||||
}
|
||||
vals := []Value{int64(0), 0.0, false, "", testDate}
|
||||
mustLoad(t, &s{}, schema, vals)
|
||||
for i, e := range vals {
|
||||
vals[i] = nil
|
||||
got := load(&s{}, schema, vals)
|
||||
@@ -1033,6 +1126,12 @@ func TestStructLoaderErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func mustLoad(t *testing.T, pval interface{}, schema Schema, vals []Value) {
|
||||
if err := load(pval, schema, vals); err != nil {
|
||||
t.Fatalf("loading: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func load(pval interface{}, schema Schema, vals []Value) error {
|
||||
var sl structLoader
|
||||
if err := sl.set(pval, schema); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user