Update vendored library cloud.google.com/go

This commit is contained in:
Alexander Neumann 2018-03-30 11:41:12 +02:00
parent a4ff591165
commit a951e7b126
221 changed files with 19911 additions and 2075 deletions

108
Gopkg.lock generated
View File

@ -4,18 +4,14 @@
[[projects]]
branch = "master"
name = "bazil.org/fuse"
packages = [
".",
"fs",
"fuseutil"
]
packages = [".","fs","fuseutil"]
revision = "371fbbdaa8987b715bdd21d6adc4c9b20155f748"
[[projects]]
name = "cloud.google.com/go"
packages = ["compute/metadata"]
revision = "767c40d6a2e058483c25fa193e963a22da17236d"
version = "v0.18.0"
revision = "4b98a6370e36d7a85192e7bad08a4ebd82eac2a8"
version = "v0.20.0"
[[projects]]
name = "github.com/Azure/azure-sdk-for-go"
@ -25,12 +21,7 @@
[[projects]]
name = "github.com/Azure/go-autorest"
packages = [
"autorest",
"autorest/adal",
"autorest/azure",
"autorest/date"
]
packages = ["autorest","autorest/adal","autorest/azure","autorest/date"]
revision = "c2a68353555b68de3ee8455a4fd3e890a0ac6d99"
version = "v9.8.1"
@ -96,12 +87,7 @@
[[projects]]
name = "github.com/kurin/blazer"
packages = [
"b2",
"base",
"internal/b2types",
"internal/blog"
]
packages = ["b2","base","internal/b2types","internal/blog"]
revision = "cd0304efa98725679cf68422cefa328d3d96f2f4"
version = "v0.3.0"
@ -112,15 +98,7 @@
[[projects]]
name = "github.com/minio/minio-go"
packages = [
".",
"pkg/credentials",
"pkg/encrypt",
"pkg/policy",
"pkg/s3signer",
"pkg/s3utils",
"pkg/set"
]
packages = [".","pkg/credentials","pkg/encrypt","pkg/policy","pkg/s3signer","pkg/s3utils","pkg/set"]
revision = "14f1d472d115bac5ca4804094aa87484a72ced61"
version = "4.0.6"
@ -186,10 +164,7 @@
[[projects]]
name = "github.com/spf13/cobra"
packages = [
".",
"doc"
]
packages = [".","doc"]
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
version = "v0.0.1"
@ -202,40 +177,19 @@
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = [
"curve25519",
"ed25519",
"ed25519/internal/edwards25519",
"internal/chacha20",
"pbkdf2",
"poly1305",
"scrypt",
"ssh",
"ssh/terminal"
]
packages = ["curve25519","ed25519","ed25519/internal/edwards25519","internal/chacha20","pbkdf2","poly1305","scrypt","ssh","ssh/terminal"]
revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = [
"context",
"context/ctxhttp",
"idna",
"lex/httplex"
]
packages = ["context","context/ctxhttp","idna","lex/httplex"]
revision = "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
[[projects]]
branch = "master"
name = "golang.org/x/oauth2"
packages = [
".",
"google",
"internal",
"jws",
"jwt"
]
packages = [".","google","internal","jws","jwt"]
revision = "b28fcf2b08a19742b43084fb40ab78ac6c3d8067"
[[projects]]
@ -247,58 +201,24 @@
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = [
"unix",
"windows"
]
packages = ["unix","windows"]
revision = "af50095a40f9041b3b38960738837185c26e9419"
[[projects]]
branch = "master"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable"
]
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3"
[[projects]]
branch = "master"
name = "google.golang.org/api"
packages = [
"gensupport",
"googleapi",
"googleapi/internal/uritemplates",
"storage/v1"
]
packages = ["gensupport","googleapi","googleapi/internal/uritemplates","storage/v1"]
revision = "65b0d8655182691ad23b4fac11e6f7b897d9b634"
[[projects]]
name = "google.golang.org/appengine"
packages = [
".",
"internal",
"internal/app_identity",
"internal/base",
"internal/datastore",
"internal/log",
"internal/modules",
"internal/remote_api",
"internal/urlfetch",
"urlfetch"
]
packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"]
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
version = "v1.0.0"

106
vendor/cloud.google.com/go/README.md generated vendored
View File

@ -33,6 +33,69 @@ make backwards-incompatible changes.
## News
_March 22, 2018_
*v0.20.*
- bigquery: Support SchemaUpdateOptions for load jobs.
- bigtable:
- Add SampleRowKeys.
- cbt: Support union, intersection GCPolicy.
- Retry admin RPCS.
- Add trace spans to retries.
- datastore: Add OpenCensus tracing.
- firestore:
- Fix queries involving Null and NaN.
- Allow Timestamp protobuffers for time values.
- logging: Add a WriteTimeout option.
- spanner: Support Batch API.
- storage: Add OpenCensus tracing.
_February 26, 2018_
*v0.19.0*
- bigquery:
- Support customer-managed encryption keys.
- bigtable:
- Improved emulator support.
- Support GetCluster.
- datastore:
- Add general mutations.
- Support pointer struct fields.
- Support transaction options.
- firestore:
- Add Transaction.GetAll.
- Support document cursors.
- logging:
- Support concurrent RPCs to the service.
- Support per-entry resources.
- profiler:
- Add config options to disable heap and thread profiling.
- Read the project ID from $GOOGLE_CLOUD_PROJECT when it's set.
- pubsub:
- BEHAVIOR CHANGE: Release flow control after ack/nack (instead of after the
callback returns).
- Add SubscriptionInProject.
- Add OpenCensus instrumentation for streaming pull.
- storage:
- Support CORS.
_January 18, 2018_
*v0.18.0*
@ -97,45 +160,6 @@ _December 11, 2017_
- TimePartitioning supports "Field".
_October 30, 2017_
*v0.16.0*
- Other bigquery changes:
- `JobIterator.Next` returns `*Job`; removed `JobInfo` (BREAKING CHANGE).
- UseStandardSQL is deprecated; set UseLegacySQL to true if you need
Legacy SQL.
- Uploader.Put will generate a random insert ID if you do not provide one.
- Support time partitioning for load jobs.
- Support dry-run queries.
- A `Job` remembers its last retrieved status.
- Support retrieving job configuration.
- Support labels for jobs and tables.
- Support dataset access lists.
- Improve support for external data sources, including data from Bigtable and
Google Sheets, and tables with external data.
- Support updating a table's view configuration.
- Fix uploading civil times with nanoseconds.
- storage:
- Support PubSub notifications.
- Support Requester Pays buckets.
- profiler: Support goroutine and mutex profile types.
_October 3, 2017_
*v0.15.0*
- firestore: beta release. See the
[announcement](https://firebase.googleblog.com/2017/10/introducing-cloud-firestore.html).
- errorreporting: The existing package has been redesigned.
- errors: This package has been removed. Use errorreporting.
[Older news](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/old-news.md)
## Supported APIs
@ -143,7 +167,7 @@ _October 3, 2017_
Google API | Status | Package
---------------------------------|--------------|-----------------------------------------------------------
[BigQuery][cloud-bigquery] | stable | [`cloud.google.com/go/bigquery`][cloud-bigquery-ref]
[Bigtable][cloud-bigtable] | beta | [`cloud.google.com/go/bigtable`][cloud-bigtable-ref]
[Bigtable][cloud-bigtable] | stable | [`cloud.google.com/go/bigtable`][cloud-bigtable-ref]
[Container][cloud-container] | alpha | [`cloud.google.com/go/container/apiv1`][cloud-container-ref]
[Data Loss Prevention][cloud-dlp]| alpha | [`cloud.google.com/go/dlp/apiv2beta1`][cloud-dlp-ref]
[Datastore][cloud-datastore] | stable | [`cloud.google.com/go/datastore`][cloud-datastore-ref]
@ -153,7 +177,7 @@ Google API | Status | Package
[Language][cloud-language] | stable | [`cloud.google.com/go/language/apiv1`][cloud-language-ref]
[Logging][cloud-logging] | stable | [`cloud.google.com/go/logging`][cloud-logging-ref]
[Monitoring][cloud-monitoring] | beta | [`cloud.google.com/go/monitoring/apiv3`][cloud-monitoring-ref]
[OS Login][cloud-oslogin] | alpha | [`cloud.google.com/compute/docs/oslogin/rest`][cloud-oslogin-ref]
[OS Login][cloud-oslogin] | alpha | [`cloud.google.com/compute/docs/oslogin/rest`][cloud-oslogin-ref]
[Pub/Sub][cloud-pubsub] | beta | [`cloud.google.com/go/pubsub`][cloud-pubsub-ref]
[Spanner][cloud-spanner] | stable | [`cloud.google.com/go/spanner`][cloud-spanner-ref]
[Speech][cloud-speech] | stable | [`cloud.google.com/go/speech/apiv1`][cloud-speech-ref]

View File

@ -47,6 +47,11 @@ func setClientHeader(headers http.Header) {
// Client may be used to perform BigQuery operations.
type Client struct {
// Location, if set, will be used as the default location for all subsequent
// dataset creation and job operations. A location specified directly in one of
// those operations will override this value.
Location string
projectID string
bqs *bq.Service
}
@ -152,5 +157,5 @@ func retryableError(err error) bool {
if len(e.Errors) > 0 {
reason = e.Errors[0].Reason
}
return reason == "backendError" || reason == "rateLimitExceeded"
return e.Code == http.StatusBadGateway || reason == "backendError" || reason == "rateLimitExceeded"
}

View File

@ -37,6 +37,9 @@ type CopyConfig struct {
// The labels associated with this job.
Labels map[string]string
// Custom encryption configuration (e.g., Cloud KMS keys).
DestinationEncryptionConfig *EncryptionConfig
}
func (c *CopyConfig) toBQ() *bq.JobConfiguration {
@ -47,10 +50,11 @@ func (c *CopyConfig) toBQ() *bq.JobConfiguration {
return &bq.JobConfiguration{
Labels: c.Labels,
Copy: &bq.JobConfigurationTableCopy{
CreateDisposition: string(c.CreateDisposition),
WriteDisposition: string(c.WriteDisposition),
DestinationTable: c.Dst.toBQ(),
SourceTables: ts,
CreateDisposition: string(c.CreateDisposition),
WriteDisposition: string(c.WriteDisposition),
DestinationTable: c.Dst.toBQ(),
DestinationEncryptionConfiguration: c.DestinationEncryptionConfig.toBQ(),
SourceTables: ts,
},
}
}
@ -61,6 +65,7 @@ func bqToCopyConfig(q *bq.JobConfiguration, c *Client) *CopyConfig {
CreateDisposition: TableCreateDisposition(q.Copy.CreateDisposition),
WriteDisposition: TableWriteDisposition(q.Copy.WriteDisposition),
Dst: bqToTable(q.Copy.DestinationTable, c),
DestinationEncryptionConfig: bqToEncryptionConfig(q.Copy.DestinationEncryptionConfiguration),
}
for _, t := range q.Copy.SourceTables {
cc.Srcs = append(cc.Srcs, bqToTable(t, c))
@ -95,7 +100,7 @@ func (c *Copier) Run(ctx context.Context) (*Job, error) {
func (c *Copier) newJob() *bq.Job {
return &bq.Job{
JobReference: c.JobIDConfig.createJobRef(c.c.projectID),
JobReference: c.JobIDConfig.createJobRef(c.c),
Configuration: c.CopyConfig.toBQ(),
}
}

View File

@ -49,11 +49,12 @@ func defaultCopyJob() *bq.Job {
func TestCopy(t *testing.T) {
defer fixRandomID("RANDOM")()
testCases := []struct {
dst *Table
srcs []*Table
jobID string
config CopyConfig
want *bq.Job
dst *Table
srcs []*Table
jobID string
location string
config CopyConfig
want *bq.Job
}{
{
dst: &Table{
@ -84,15 +85,17 @@ func TestCopy(t *testing.T) {
},
},
config: CopyConfig{
CreateDisposition: CreateNever,
WriteDisposition: WriteTruncate,
Labels: map[string]string{"a": "b"},
CreateDisposition: CreateNever,
WriteDisposition: WriteTruncate,
DestinationEncryptionConfig: &EncryptionConfig{KMSKeyName: "keyName"},
Labels: map[string]string{"a": "b"},
},
want: func() *bq.Job {
j := defaultCopyJob()
j.Configuration.Labels = map[string]string{"a": "b"}
j.Configuration.Copy.CreateDisposition = "CREATE_NEVER"
j.Configuration.Copy.WriteDisposition = "WRITE_TRUNCATE"
j.Configuration.Copy.DestinationEncryptionConfiguration = &bq.EncryptionConfiguration{KmsKeyName: "keyName"}
return j
}(),
},
@ -116,12 +119,33 @@ func TestCopy(t *testing.T) {
return j
}(),
},
{
dst: &Table{
ProjectID: "d-project-id",
DatasetID: "d-dataset-id",
TableID: "d-table-id",
},
srcs: []*Table{
{
ProjectID: "s-project-id",
DatasetID: "s-dataset-id",
TableID: "s-table-id",
},
},
location: "asia-northeast1",
want: func() *bq.Job {
j := defaultCopyJob()
j.JobReference.Location = "asia-northeast1"
return j
}(),
},
}
c := &Client{projectID: "client-project-id"}
for i, tc := range testCases {
tc.dst.c = c
copier := tc.dst.CopierFrom(tc.srcs...)
copier.JobID = tc.jobID
copier.Location = tc.location
tc.config.Srcs = tc.srcs
tc.config.Dst = tc.dst
copier.CopyConfig = tc.config

View File

@ -91,6 +91,10 @@ func (d *Dataset) Create(ctx context.Context, md *DatasetMetadata) error {
return err
}
ds.DatasetReference = &bq.DatasetReference{DatasetId: d.DatasetID}
// Use Client.Location as a default.
if ds.Location == "" {
ds.Location = d.c.Location
}
call := d.c.bqs.Datasets.Insert(d.ProjectID, ds).Context(ctx)
setClientHeader(call.Header())
_, err = call.Do()

View File

@ -55,7 +55,7 @@ func TestDataTransferServiceSmoke(t *testing.T) {
t.Fatal(err)
}
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s", projectId, "us-central1")
var formattedParent string = fmt.Sprintf("projects/%s", projectId)
var request = &datatransferpb.ListDataSourcesRequest{
Parent: formattedParent,
}

View File

@ -294,7 +294,7 @@ func (c *Client) ListTransferConfigs(ctx context.Context, req *datatransferpb.Li
return it
}
// ScheduleTransferRuns creates transfer runs for a time range [range_start_time, range_end_time].
// ScheduleTransferRuns creates transfer runs for a time range [start_time, 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.

View File

@ -42,8 +42,6 @@ func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
// 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",
}
}

View File

@ -281,7 +281,7 @@ func TestDataTransferServiceGetDataSource(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/dataSources/%s", "[PROJECT]", "[LOCATION]", "[DATA_SOURCE]")
var formattedName string = fmt.Sprintf("projects/%s/dataSources/%s", "[PROJECT]", "[DATA_SOURCE]")
var request = &datatransferpb.GetDataSourceRequest{
Name: formattedName,
}
@ -310,7 +310,7 @@ func TestDataTransferServiceGetDataSourceError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/dataSources/%s", "[PROJECT]", "[LOCATION]", "[DATA_SOURCE]")
var formattedName string = fmt.Sprintf("projects/%s/dataSources/%s", "[PROJECT]", "[DATA_SOURCE]")
var request = &datatransferpb.GetDataSourceRequest{
Name: formattedName,
}
@ -343,7 +343,7 @@ func TestDataTransferServiceListDataSources(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s", "[PROJECT]", "[LOCATION]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &datatransferpb.ListDataSourcesRequest{
Parent: formattedParent,
}
@ -382,7 +382,7 @@ func TestDataTransferServiceListDataSourcesError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s", "[PROJECT]", "[LOCATION]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &datatransferpb.ListDataSourcesRequest{
Parent: formattedParent,
}
@ -428,7 +428,7 @@ func TestDataTransferServiceCreateTransferConfig(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s", "[PROJECT]", "[LOCATION]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var transferConfig *datatransferpb.TransferConfig = &datatransferpb.TransferConfig{}
var request = &datatransferpb.CreateTransferConfigRequest{
Parent: formattedParent,
@ -459,7 +459,7 @@ func TestDataTransferServiceCreateTransferConfigError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s", "[PROJECT]", "[LOCATION]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var transferConfig *datatransferpb.TransferConfig = &datatransferpb.TransferConfig{}
var request = &datatransferpb.CreateTransferConfigRequest{
Parent: formattedParent,
@ -567,7 +567,7 @@ func TestDataTransferServiceDeleteTransferConfig(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]")
var formattedName string = fmt.Sprintf("projects/%s/transferConfigs/%s", "[PROJECT]", "[TRANSFER_CONFIG]")
var request = &datatransferpb.DeleteTransferConfigRequest{
Name: formattedName,
}
@ -593,7 +593,7 @@ func TestDataTransferServiceDeleteTransferConfigError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]")
var formattedName string = fmt.Sprintf("projects/%s/transferConfigs/%s", "[PROJECT]", "[TRANSFER_CONFIG]")
var request = &datatransferpb.DeleteTransferConfigRequest{
Name: formattedName,
}
@ -638,7 +638,7 @@ func TestDataTransferServiceGetTransferConfig(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]")
var formattedName string = fmt.Sprintf("projects/%s/transferConfigs/%s", "[PROJECT]", "[TRANSFER_CONFIG]")
var request = &datatransferpb.GetTransferConfigRequest{
Name: formattedName,
}
@ -667,7 +667,7 @@ func TestDataTransferServiceGetTransferConfigError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]")
var formattedName string = fmt.Sprintf("projects/%s/transferConfigs/%s", "[PROJECT]", "[TRANSFER_CONFIG]")
var request = &datatransferpb.GetTransferConfigRequest{
Name: formattedName,
}
@ -700,7 +700,7 @@ func TestDataTransferServiceListTransferConfigs(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s", "[PROJECT]", "[LOCATION]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &datatransferpb.ListTransferConfigsRequest{
Parent: formattedParent,
}
@ -739,7 +739,7 @@ func TestDataTransferServiceListTransferConfigsError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s", "[PROJECT]", "[LOCATION]")
var formattedParent string = fmt.Sprintf("projects/%s", "[PROJECT]")
var request = &datatransferpb.ListTransferConfigsRequest{
Parent: formattedParent,
}
@ -766,7 +766,7 @@ func TestDataTransferServiceScheduleTransferRuns(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]")
var formattedParent string = fmt.Sprintf("projects/%s/transferConfigs/%s", "[PROJECT]", "[TRANSFER_CONFIG]")
var startTime *timestamppb.Timestamp = &timestamppb.Timestamp{}
var endTime *timestamppb.Timestamp = &timestamppb.Timestamp{}
var request = &datatransferpb.ScheduleTransferRunsRequest{
@ -799,7 +799,7 @@ func TestDataTransferServiceScheduleTransferRunsError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]")
var formattedParent string = fmt.Sprintf("projects/%s/transferConfigs/%s", "[PROJECT]", "[TRANSFER_CONFIG]")
var startTime *timestamppb.Timestamp = &timestamppb.Timestamp{}
var endTime *timestamppb.Timestamp = &timestamppb.Timestamp{}
var request = &datatransferpb.ScheduleTransferRunsRequest{
@ -841,7 +841,7 @@ func TestDataTransferServiceGetTransferRun(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]", "[RUN]")
var formattedName string = fmt.Sprintf("projects/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[TRANSFER_CONFIG]", "[RUN]")
var request = &datatransferpb.GetTransferRunRequest{
Name: formattedName,
}
@ -870,7 +870,7 @@ func TestDataTransferServiceGetTransferRunError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]", "[RUN]")
var formattedName string = fmt.Sprintf("projects/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[TRANSFER_CONFIG]", "[RUN]")
var request = &datatransferpb.GetTransferRunRequest{
Name: formattedName,
}
@ -897,7 +897,7 @@ func TestDataTransferServiceDeleteTransferRun(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]", "[RUN]")
var formattedName string = fmt.Sprintf("projects/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[TRANSFER_CONFIG]", "[RUN]")
var request = &datatransferpb.DeleteTransferRunRequest{
Name: formattedName,
}
@ -923,7 +923,7 @@ func TestDataTransferServiceDeleteTransferRunError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]", "[RUN]")
var formattedName string = fmt.Sprintf("projects/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[TRANSFER_CONFIG]", "[RUN]")
var request = &datatransferpb.DeleteTransferRunRequest{
Name: formattedName,
}
@ -955,7 +955,7 @@ func TestDataTransferServiceListTransferRuns(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]")
var formattedParent string = fmt.Sprintf("projects/%s/transferConfigs/%s", "[PROJECT]", "[TRANSFER_CONFIG]")
var request = &datatransferpb.ListTransferRunsRequest{
Parent: formattedParent,
}
@ -994,7 +994,7 @@ func TestDataTransferServiceListTransferRunsError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]")
var formattedParent string = fmt.Sprintf("projects/%s/transferConfigs/%s", "[PROJECT]", "[TRANSFER_CONFIG]")
var request = &datatransferpb.ListTransferRunsRequest{
Parent: formattedParent,
}
@ -1027,7 +1027,7 @@ func TestDataTransferServiceListTransferLogs(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]", "[RUN]")
var formattedParent string = fmt.Sprintf("projects/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[TRANSFER_CONFIG]", "[RUN]")
var request = &datatransferpb.ListTransferLogsRequest{
Parent: formattedParent,
}
@ -1066,7 +1066,7 @@ func TestDataTransferServiceListTransferLogsError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedParent string = fmt.Sprintf("projects/%s/locations/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[LOCATION]", "[TRANSFER_CONFIG]", "[RUN]")
var formattedParent string = fmt.Sprintf("projects/%s/transferConfigs/%s/runs/%s", "[PROJECT]", "[TRANSFER_CONFIG]", "[RUN]")
var request = &datatransferpb.ListTransferLogsRequest{
Parent: formattedParent,
}
@ -1096,7 +1096,7 @@ func TestDataTransferServiceCheckValidCreds(t *testing.T) {
mockDataTransfer.resps = append(mockDataTransfer.resps[:0], expectedResponse)
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/dataSources/%s", "[PROJECT]", "[LOCATION]", "[DATA_SOURCE]")
var formattedName string = fmt.Sprintf("projects/%s/dataSources/%s", "[PROJECT]", "[DATA_SOURCE]")
var request = &datatransferpb.CheckValidCredsRequest{
Name: formattedName,
}
@ -1125,7 +1125,7 @@ func TestDataTransferServiceCheckValidCredsError(t *testing.T) {
errCode := codes.PermissionDenied
mockDataTransfer.err = gstatus.Error(errCode, "test error")
var formattedName string = fmt.Sprintf("projects/%s/locations/%s/dataSources/%s", "[PROJECT]", "[LOCATION]", "[DATA_SOURCE]")
var formattedName string = fmt.Sprintf("projects/%s/dataSources/%s", "[PROJECT]", "[DATA_SOURCE]")
var request = &datatransferpb.CheckValidCredsRequest{
Name: formattedName,
}

View File

@ -131,6 +131,23 @@ func ExampleClient_Query_parameters() {
// TODO: Call Query.Run or Query.Read.
}
// This example demonstrates how to run a query job on a table
// with a customer-managed encryption key. The same
// applies to load and copy jobs as well.
func ExampleClient_Query_encryptionKey() {
ctx := context.Background()
client, err := bigquery.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
q := client.Query("select name, num from t1")
// TODO: Replace this key with a key you have created in Cloud KMS.
keyName := "projects/P/locations/L/keyRings/R/cryptoKeys/K"
q.DestinationEncryptionConfig = &bigquery.EncryptionConfig{KMSKeyName: keyName}
// TODO: set other options on the Query.
// TODO: Call Query.Run or Query.Read.
}
func ExampleQuery_Read() {
ctx := context.Background()
client, err := bigquery.NewClient(ctx, "project-id")
@ -453,6 +470,33 @@ func ExampleTable_Create_initialize() {
}
}
// This example demonstrates how to create a table with
// a customer-managed encryption key.
func ExampleTable_Create_encryptionKey() {
ctx := context.Background()
// Infer table schema from a Go type.
schema, err := bigquery.InferSchema(Item{})
if err != nil {
// TODO: Handle error.
}
client, err := bigquery.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
t := client.Dataset("my_dataset").Table("new-table")
// TODO: Replace this key with a key you have created in Cloud KMS.
keyName := "projects/P/locations/L/keyRings/R/cryptoKeys/K"
if err := t.Create(ctx,
&bigquery.TableMetadata{
Name: "My New Table",
Schema: schema,
EncryptionConfig: &bigquery.EncryptionConfig{KMSKeyName: keyName},
}); err != nil {
// TODO: Handle error.
}
}
func ExampleTable_Delete() {
ctx := context.Background()
client, err := bigquery.NewClient(ctx, "project-id")

View File

@ -99,7 +99,7 @@ func (e *Extractor) Run(ctx context.Context) (*Job, error) {
func (e *Extractor) newJob() *bq.Job {
return &bq.Job{
JobReference: e.JobIDConfig.createJobRef(e.c.projectID),
JobReference: e.JobIDConfig.createJobRef(e.c),
Configuration: e.ExtractConfig.toBQ(),
}
}

View File

@ -59,6 +59,9 @@ var (
tableIDs = testutil.NewUIDSpaceSep("table", '_')
)
// Note: integration tests cannot be run in parallel, because TestIntegration_Location
// modifies the client.
func TestMain(m *testing.M) {
cleanup := initIntegrationTest()
r := m.Run()
@ -691,14 +694,14 @@ func TestIntegration_UploadAndReadStructs(t *testing.T) {
table := newTable(t, schema)
defer table.Delete(ctx)
d := civil.Date{2016, 3, 20}
tm := civil.Time{15, 4, 5, 6000}
d := civil.Date{Year: 2016, Month: 3, Day: 20}
tm := civil.Time{Hour: 15, Minute: 4, Second: 5, Nanosecond: 6000}
ts := time.Date(2016, 3, 20, 15, 4, 5, 6000, time.UTC)
dtm := civil.DateTime{d, tm}
d2 := civil.Date{1994, 5, 15}
tm2 := civil.Time{1, 2, 4, 0}
dtm := civil.DateTime{Date: d, Time: tm}
d2 := civil.Date{Year: 1994, Month: 5, Day: 15}
tm2 := civil.Time{Hour: 1, Minute: 2, Second: 4, Nanosecond: 0}
ts2 := time.Date(1994, 5, 15, 1, 2, 4, 0, time.UTC)
dtm2 := civil.DateTime{d2, tm2}
dtm2 := civil.DateTime{Date: d2, Time: tm2}
// Populate the table.
upl := table.Uploader()
@ -797,8 +800,8 @@ 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}
ctm := civil.Time{Hour: 15, Minute: 4, Second: 5, Nanosecond: 6000}
cdt := civil.DateTime{Date: testDate, Time: ctm}
testUploadAndReadNullable(t, testStructNullable{}, make([]Value, len(testStructNullableSchema)))
testUploadAndReadNullable(t, testStructNullable{
String: NullString{"x", true},
@ -943,23 +946,23 @@ func TestIntegration_TableUpdate(t *testing.T) {
// Error cases when updating schema.
for _, test := range []struct {
desc string
fields []*FieldSchema
fields Schema
}{
{"change from optional to required", []*FieldSchema{
{"change from optional to required", Schema{
{Name: "name", Type: StringFieldType, Required: true},
schema3[1],
schema3[2],
schema3[3],
}},
{"add a required field", []*FieldSchema{
{"add a required field", Schema{
schema3[0], schema3[1], schema3[2], schema3[3],
{Name: "req", Type: StringFieldType, Required: true},
}},
{"remove a field", []*FieldSchema{schema3[0], schema3[1], schema3[2]}},
{"remove a nested field", []*FieldSchema{
{"remove a field", Schema{schema3[0], schema3[1], schema3[2]}},
{"remove a nested field", Schema{
schema3[0], schema3[1], schema3[2],
{Name: "rec2", Type: RecordFieldType, Schema: Schema{nested[0]}}}},
{"remove all nested fields", []*FieldSchema{
{"remove all nested fields", Schema{
schema3[0], schema3[1], schema3[2],
{Name: "rec2", Type: RecordFieldType, Schema: Schema{}}}},
} {
@ -1085,9 +1088,9 @@ func TestIntegration_TimeTypes(t *testing.T) {
table := newTable(t, dtSchema)
defer table.Delete(ctx)
d := civil.Date{2016, 3, 20}
tm := civil.Time{12, 30, 0, 6000}
dtm := civil.DateTime{d, tm}
d := civil.Date{Year: 2016, Month: 3, Day: 20}
tm := civil.Time{Hour: 12, Minute: 30, Second: 0, Nanosecond: 6000}
dtm := civil.DateTime{Date: d, Time: tm}
ts := time.Date(2016, 3, 20, 15, 04, 05, 0, time.UTC)
wantRows := [][]Value{
[]Value{d, tm, dtm, ts},
@ -1121,8 +1124,8 @@ func TestIntegration_StandardQuery(t *testing.T) {
}
ctx := context.Background()
d := civil.Date{2016, 3, 20}
tm := civil.Time{15, 04, 05, 0}
d := civil.Date{Year: 2016, Month: 3, Day: 20}
tm := civil.Time{Hour: 15, Minute: 04, Second: 05, Nanosecond: 0}
ts := time.Date(2016, 3, 20, 15, 04, 05, 0, time.UTC)
dtm := ts.Format("2006-01-02 15:04:05")
@ -1147,7 +1150,7 @@ func TestIntegration_StandardQuery(t *testing.T) {
{fmt.Sprintf("SELECT TIMESTAMP '%s'", dtm), []Value{ts}},
{fmt.Sprintf("SELECT [TIMESTAMP '%s', TIMESTAMP '%s']", dtm, dtm), []Value{[]Value{ts, ts}}},
{fmt.Sprintf("SELECT ('hello', TIMESTAMP '%s')", dtm), []Value{[]Value{"hello", ts}}},
{fmt.Sprintf("SELECT DATETIME(TIMESTAMP '%s')", dtm), []Value{civil.DateTime{d, tm}}},
{fmt.Sprintf("SELECT DATETIME(TIMESTAMP '%s')", dtm), []Value{civil.DateTime{Date: d, Time: tm}}},
{fmt.Sprintf("SELECT DATE(TIMESTAMP '%s')", dtm), []Value{d}},
{fmt.Sprintf("SELECT TIME(TIMESTAMP '%s')", dtm), []Value{tm}},
{"SELECT (1, 2)", []Value{ints(1, 2)}},
@ -1206,9 +1209,11 @@ func TestIntegration_QueryParameters(t *testing.T) {
}
ctx := context.Background()
d := civil.Date{2016, 3, 20}
tm := civil.Time{15, 04, 05, 0}
dtm := civil.DateTime{d, tm}
d := civil.Date{Year: 2016, Month: 3, Day: 20}
tm := civil.Time{Hour: 15, Minute: 04, Second: 05, Nanosecond: 3008}
rtm := tm
rtm.Nanosecond = 3000 // round to microseconds
dtm := civil.DateTime{Date: d, Time: tm}
ts := time.Date(2016, 3, 20, 15, 04, 05, 0, time.UTC)
type ss struct {
@ -1226,20 +1231,93 @@ func TestIntegration_QueryParameters(t *testing.T) {
query string
parameters []QueryParameter
wantRow []Value
wantConfig interface{}
}{
{"SELECT @val", []QueryParameter{{"val", 1}}, []Value{int64(1)}},
{"SELECT @val", []QueryParameter{{"val", 1.3}}, []Value{1.3}},
{"SELECT @val", []QueryParameter{{"val", true}}, []Value{true}},
{"SELECT @val", []QueryParameter{{"val", "ABC"}}, []Value{"ABC"}},
{"SELECT @val", []QueryParameter{{"val", []byte("foo")}}, []Value{[]byte("foo")}},
{"SELECT @val", []QueryParameter{{"val", ts}}, []Value{ts}},
{"SELECT @val", []QueryParameter{{"val", []time.Time{ts, ts}}}, []Value{[]Value{ts, ts}}},
{"SELECT @val", []QueryParameter{{"val", dtm}}, []Value{dtm}},
{"SELECT @val", []QueryParameter{{"val", d}}, []Value{d}},
{"SELECT @val", []QueryParameter{{"val", tm}}, []Value{tm}},
{"SELECT @val", []QueryParameter{{"val", s{ts, []string{"a", "b"}, ss{"c"}, []ss{{"d"}, {"e"}}}}},
[]Value{[]Value{ts, []Value{"a", "b"}, []Value{"c"}, []Value{[]Value{"d"}, []Value{"e"}}}}},
{"SELECT @val.Timestamp, @val.SubStruct.String", []QueryParameter{{"val", s{Timestamp: ts, SubStruct: ss{"a"}}}}, []Value{ts, "a"}},
{
"SELECT @val",
[]QueryParameter{{"val", 1}},
[]Value{int64(1)},
int64(1),
},
{
"SELECT @val",
[]QueryParameter{{"val", 1.3}},
[]Value{1.3},
1.3,
},
{
"SELECT @val",
[]QueryParameter{{"val", true}},
[]Value{true},
true,
},
{
"SELECT @val",
[]QueryParameter{{"val", "ABC"}},
[]Value{"ABC"},
"ABC",
},
{
"SELECT @val",
[]QueryParameter{{"val", []byte("foo")}},
[]Value{[]byte("foo")},
[]byte("foo"),
},
{
"SELECT @val",
[]QueryParameter{{"val", ts}},
[]Value{ts},
ts,
},
{
"SELECT @val",
[]QueryParameter{{"val", []time.Time{ts, ts}}},
[]Value{[]Value{ts, ts}},
[]interface{}{ts, ts},
},
{
"SELECT @val",
[]QueryParameter{{"val", dtm}},
[]Value{civil.DateTime{Date: d, Time: rtm}},
civil.DateTime{Date: d, Time: rtm},
},
{
"SELECT @val",
[]QueryParameter{{"val", d}},
[]Value{d},
d,
},
{
"SELECT @val",
[]QueryParameter{{"val", tm}},
[]Value{rtm},
rtm,
},
{
"SELECT @val",
[]QueryParameter{{"val", s{ts, []string{"a", "b"}, ss{"c"}, []ss{{"d"}, {"e"}}}}},
[]Value{[]Value{ts, []Value{"a", "b"}, []Value{"c"}, []Value{[]Value{"d"}, []Value{"e"}}}},
map[string]interface{}{
"Timestamp": ts,
"StringArray": []interface{}{"a", "b"},
"SubStruct": map[string]interface{}{"String": "c"},
"SubStructArray": []interface{}{
map[string]interface{}{"String": "d"},
map[string]interface{}{"String": "e"},
},
},
},
{
"SELECT @val.Timestamp, @val.SubStruct.String",
[]QueryParameter{{"val", s{Timestamp: ts, SubStruct: ss{"a"}}}},
[]Value{ts, "a"},
map[string]interface{}{
"Timestamp": ts,
"SubStruct": map[string]interface{}{"String": "a"},
"StringArray": nil,
"SubStructArray": nil,
},
},
}
for _, c := range testCases {
q := client.Query(c.query)
@ -1256,6 +1334,15 @@ func TestIntegration_QueryParameters(t *testing.T) {
t.Fatal(err)
}
checkRead(t, "QueryParameters", it, [][]Value{c.wantRow})
config, err := job.Config()
if err != nil {
t.Fatal(err)
}
got := config.(*QueryConfig).Parameters[0].Value
if !testutil.Equal(got, c.wantConfig) {
t.Errorf("param %[1]v (%[1]T): config:\ngot %[2]v (%[2]T)\nwant %[3]v (%[3]T)",
c.parameters[0].Value, got, c.wantConfig)
}
}
}
@ -1519,6 +1606,117 @@ func TestIntegration_ListJobs(t *testing.T) {
}
}
const tokyo = "asia-northeast1"
func TestIntegration_Location(t *testing.T) {
if client == nil {
t.Skip("Integration tests skipped")
}
client.Location = ""
testLocation(t, tokyo)
client.Location = tokyo
defer func() {
client.Location = ""
}()
testLocation(t, "")
}
func testLocation(t *testing.T, loc string) {
ctx := context.Background()
tokyoDataset := client.Dataset("tokyo")
err := tokyoDataset.Create(ctx, &DatasetMetadata{Location: loc})
if err != nil && !hasStatusCode(err, 409) { // 409 = already exists
t.Fatal(err)
}
md, err := tokyoDataset.Metadata(ctx)
if err != nil {
t.Fatal(err)
}
if md.Location != tokyo {
t.Fatalf("dataset location: got %s, want %s", md.Location, tokyo)
}
table := tokyoDataset.Table(tableIDs.New())
err = table.Create(context.Background(), &TableMetadata{
Schema: Schema{
{Name: "name", Type: StringFieldType},
{Name: "nums", Type: IntegerFieldType},
},
ExpirationTime: testTableExpiration,
})
if err != nil {
t.Fatal(err)
}
defer table.Delete(ctx)
loader := table.LoaderFrom(NewReaderSource(strings.NewReader("a,0\nb,1\nc,2\n")))
loader.Location = loc
job, err := loader.Run(ctx)
if err != nil {
t.Fatal("loader.Run", err)
}
if job.Location() != tokyo {
t.Fatalf("job location: got %s, want %s", job.Location(), tokyo)
}
_, err = client.JobFromID(ctx, job.ID())
if client.Location == "" && err == nil {
t.Error("JobFromID with Tokyo job, no client location: want error, got nil")
}
if client.Location != "" && err != nil {
t.Errorf("JobFromID with Tokyo job, with client location: want nil, got %v", err)
}
_, err = client.JobFromIDLocation(ctx, job.ID(), "US")
if err == nil {
t.Error("JobFromIDLocation with US: want error, got nil")
}
job2, err := client.JobFromIDLocation(ctx, job.ID(), loc)
if loc == tokyo && err != nil {
t.Errorf("loc=tokyo: %v", err)
}
if loc == "" && err == nil {
t.Error("loc empty: got nil, want error")
}
if job2 != nil && (job2.ID() != job.ID() || job2.Location() != tokyo) {
t.Errorf("got id %s loc %s, want id%s loc %s", job2.ID(), job2.Location(), job.ID(), tokyo)
}
if err := wait(ctx, job); err != nil {
t.Fatal(err)
}
// Cancel should succeed even if the job is done.
if err := job.Cancel(ctx); err != nil {
t.Fatal(err)
}
q := client.Query(fmt.Sprintf("SELECT * FROM %s.%s", table.DatasetID, table.TableID))
q.Location = loc
iter, err := q.Read(ctx)
if err != nil {
t.Fatal(err)
}
wantRows := [][]Value{
[]Value{"a", int64(0)},
[]Value{"b", int64(1)},
[]Value{"c", int64(2)},
}
checkRead(t, "location", iter, wantRows)
table2 := tokyoDataset.Table(tableIDs.New())
copier := table2.CopierFrom(table)
copier.Location = loc
if _, err := copier.Run(ctx); err != nil {
t.Fatal(err)
}
bucketName := testutil.ProjID()
objectName := fmt.Sprintf("bq-test-%s.csv", table.TableID)
uri := fmt.Sprintf("gs://%s/%s", bucketName, objectName)
defer storageClient.Bucket(bucketName).Object(objectName).Delete(ctx)
gr := NewGCSReference(uri)
gr.DestinationFormat = CSV
e := table.ExtractorTo(gr)
e.Location = loc
if _, err := e.Run(ctx); err != nil {
t.Fatal(err)
}
}
// Creates a new, temporary table with a unique name and the given schema.
func newTable(t *testing.T, s Schema) *Table {
table := dataset.Table(tableIDs.New())

View File

@ -35,6 +35,7 @@ type Job struct {
c *Client
projectID string
jobID string
location string
config *bq.JobConfiguration
lastStatus *JobStatus
@ -43,8 +44,18 @@ type Job struct {
// JobFromID creates a Job which refers to an existing BigQuery job. The job
// need not have been created by this package. For example, the job may have
// been created in the BigQuery console.
//
// For jobs whose location is other than "US" or "EU", set Client.Location or use
// JobFromIDLocation.
func (c *Client) JobFromID(ctx context.Context, id string) (*Job, error) {
bqjob, err := c.getJobInternal(ctx, id, "configuration", "jobReference", "status", "statistics")
return c.JobFromIDLocation(ctx, id, c.Location)
}
// JobFromIDLocation creates a Job which refers to an existing BigQuery job. The job
// need not have been created by this package (for example, it may have
// been created in the BigQuery console), but it must exist in the specified location.
func (c *Client) JobFromIDLocation(ctx context.Context, id, location string) (*Job, error) {
bqjob, err := c.getJobInternal(ctx, id, location, "configuration", "jobReference", "status", "statistics")
if err != nil {
return nil, err
}
@ -56,6 +67,11 @@ func (j *Job) ID() string {
return j.jobID
}
// Location returns the job's location.
func (j *Job) Location() string {
return j.location
}
// State is one of a sequence of states that a Job progresses through as it is processed.
type State int
@ -120,14 +136,20 @@ type JobIDConfig struct {
// If AddJobIDSuffix is true, then a random string will be appended to JobID.
AddJobIDSuffix bool
// Location is the location for the job.
Location string
}
// createJobRef creates a JobReference.
// projectID must be non-empty.
func (j *JobIDConfig) createJobRef(projectID string) *bq.JobReference {
func (j *JobIDConfig) createJobRef(c *Client) *bq.JobReference {
// We don't check whether projectID is empty; the server will return an
// error when it encounters the resulting JobReference.
jr := &bq.JobReference{ProjectId: projectID}
loc := j.Location
if loc == "" { // Use Client.Location as a default.
loc = c.Location
}
jr := &bq.JobReference{ProjectId: c.projectID, Location: loc}
if j.JobID == "" {
jr.JobId = randomIDFn()
} else if j.AddJobIDSuffix {
@ -176,7 +198,7 @@ func (s *JobStatus) Err() error {
// Status retrieves the current status of the job from BigQuery. It fails if the Status could not be determined.
func (j *Job) Status(ctx context.Context) (*JobStatus, error) {
bqjob, err := j.c.getJobInternal(ctx, j.jobID, "status", "statistics")
bqjob, err := j.c.getJobInternal(ctx, j.jobID, j.location, "status", "statistics")
if err != nil {
return nil, err
}
@ -204,6 +226,7 @@ func (j *Job) Cancel(ctx context.Context) error {
// to poll for the job status to see if the cancel completed
// successfully". So it would be misleading to return a status.
call := j.c.bqs.Jobs.Cancel(j.projectID, j.jobID).
Location(j.location).
Fields(). // We don't need any of the response data.
Context(ctx)
setClientHeader(call.Header())
@ -261,14 +284,17 @@ func (j *Job) read(ctx context.Context, waitForQuery func(context.Context, strin
}
destTable := j.config.Query.DestinationTable
// The destination table should only be nil if there was a query error.
if destTable == nil {
return nil, errors.New("bigquery: query job missing destination table")
projectID := j.projectID
if destTable != nil && projectID != destTable.ProjectId {
return nil, fmt.Errorf("bigquery: job project ID is %q, but destination table's is %q", projectID, destTable.ProjectId)
}
projectID := destTable.ProjectId
schema, err := waitForQuery(ctx, projectID)
if err != nil {
return nil, err
}
if destTable == nil {
return nil, errors.New("bigquery: query job missing destination table")
}
dt := bqToTable(destTable, j.c)
it := newRowIterator(ctx, dt, pf)
it.Schema = schema
@ -278,7 +304,7 @@ func (j *Job) read(ctx context.Context, waitForQuery func(context.Context, strin
// waitForQuery waits for the query job to complete and returns its schema.
func (j *Job) waitForQuery(ctx context.Context, projectID string) (Schema, error) {
// Use GetQueryResults only to wait for completion, not to read results.
call := j.c.bqs.Jobs.GetQueryResults(projectID, j.jobID).Context(ctx).MaxResults(0)
call := j.c.bqs.Jobs.GetQueryResults(projectID, j.jobID).Location(j.location).Context(ctx).MaxResults(0)
setClientHeader(call.Header())
backoff := gax.Backoff{
Initial: 1 * time.Second,
@ -522,9 +548,12 @@ func convertListedJob(j *bq.JobListJobs, c *Client) (*Job, error) {
return bqToJob2(j.JobReference, j.Configuration, j.Status, j.Statistics, c)
}
func (c *Client) getJobInternal(ctx context.Context, jobID string, fields ...googleapi.Field) (*bq.Job, error) {
func (c *Client) getJobInternal(ctx context.Context, jobID, location string, fields ...googleapi.Field) (*bq.Job, error) {
var job *bq.Job
call := c.bqs.Jobs.Get(c.projectID, jobID).Context(ctx)
if location != "" {
call = call.Location(location)
}
if len(fields) > 0 {
call = call.Fields(fields...)
}
@ -547,6 +576,7 @@ func bqToJob2(qr *bq.JobReference, qc *bq.JobConfiguration, qs *bq.JobStatus, qt
j := &Job{
projectID: qr.ProjectId,
jobID: qr.JobId,
location: qr.Location,
c: c,
}
j.setConfig(qc)

View File

@ -23,37 +23,52 @@ import (
func TestCreateJobRef(t *testing.T) {
defer fixRandomID("RANDOM")()
cNoLoc := &Client{projectID: "projectID"}
cLoc := &Client{projectID: "projectID", Location: "defaultLoc"}
for _, test := range []struct {
jobID string
addJobIDSuffix bool
want string
in JobIDConfig
client *Client
want *bq.JobReference
}{
{
jobID: "foo",
addJobIDSuffix: false,
want: "foo",
in: JobIDConfig{JobID: "foo"},
want: &bq.JobReference{JobId: "foo"},
},
{
jobID: "",
addJobIDSuffix: false,
want: "RANDOM",
in: JobIDConfig{},
want: &bq.JobReference{JobId: "RANDOM"},
},
{
jobID: "",
addJobIDSuffix: true, // irrelevant
want: "RANDOM",
in: JobIDConfig{AddJobIDSuffix: true},
want: &bq.JobReference{JobId: "RANDOM"},
},
{
jobID: "foo",
addJobIDSuffix: true,
want: "foo-RANDOM",
in: JobIDConfig{JobID: "foo", AddJobIDSuffix: true},
want: &bq.JobReference{JobId: "foo-RANDOM"},
},
{
in: JobIDConfig{JobID: "foo", Location: "loc"},
want: &bq.JobReference{JobId: "foo", Location: "loc"},
},
{
in: JobIDConfig{JobID: "foo"},
client: cLoc,
want: &bq.JobReference{JobId: "foo", Location: "defaultLoc"},
},
{
in: JobIDConfig{JobID: "foo", Location: "loc"},
client: cLoc,
want: &bq.JobReference{JobId: "foo", Location: "loc"},
},
} {
jc := JobIDConfig{JobID: test.jobID, AddJobIDSuffix: test.addJobIDSuffix}
jr := jc.createJobRef("projectID")
got := jr.JobId
if got != test.want {
t.Errorf("%q, %t: got %q, want %q", test.jobID, test.addJobIDSuffix, got, test.want)
client := test.client
if client == nil {
client = cNoLoc
}
got := test.in.createJobRef(client)
test.want.ProjectId = "projectID"
if !testutil.Equal(got, test.want) {
t.Errorf("%+v: got %+v, want %+v", test.in, got, test.want)
}
}
}

View File

@ -42,16 +42,25 @@ type LoadConfig struct {
// If non-nil, the destination table is partitioned by time.
TimePartitioning *TimePartitioning
// Custom encryption configuration (e.g., Cloud KMS keys).
DestinationEncryptionConfig *EncryptionConfig
// SchemaUpdateOptions allows the schema of the destination table to be
// updated as a side effect of the load job.
SchemaUpdateOptions []string
}
func (l *LoadConfig) toBQ() (*bq.JobConfiguration, io.Reader) {
config := &bq.JobConfiguration{
Labels: l.Labels,
Load: &bq.JobConfigurationLoad{
CreateDisposition: string(l.CreateDisposition),
WriteDisposition: string(l.WriteDisposition),
DestinationTable: l.Dst.toBQ(),
TimePartitioning: l.TimePartitioning.toBQ(),
CreateDisposition: string(l.CreateDisposition),
WriteDisposition: string(l.WriteDisposition),
DestinationTable: l.Dst.toBQ(),
TimePartitioning: l.TimePartitioning.toBQ(),
DestinationEncryptionConfiguration: l.DestinationEncryptionConfig.toBQ(),
SchemaUpdateOptions: l.SchemaUpdateOptions,
},
}
media := l.Src.populateLoadConfig(config.Load)
@ -60,11 +69,13 @@ func (l *LoadConfig) toBQ() (*bq.JobConfiguration, io.Reader) {
func bqToLoadConfig(q *bq.JobConfiguration, c *Client) *LoadConfig {
lc := &LoadConfig{
Labels: q.Labels,
CreateDisposition: TableCreateDisposition(q.Load.CreateDisposition),
WriteDisposition: TableWriteDisposition(q.Load.WriteDisposition),
Dst: bqToTable(q.Load.DestinationTable, c),
TimePartitioning: bqToTimePartitioning(q.Load.TimePartitioning),
Labels: q.Labels,
CreateDisposition: TableCreateDisposition(q.Load.CreateDisposition),
WriteDisposition: TableWriteDisposition(q.Load.WriteDisposition),
Dst: bqToTable(q.Load.DestinationTable, c),
TimePartitioning: bqToTimePartitioning(q.Load.TimePartitioning),
DestinationEncryptionConfig: bqToEncryptionConfig(q.Load.DestinationEncryptionConfiguration),
SchemaUpdateOptions: q.Load.SchemaUpdateOptions,
}
var fc *FileConfig
if len(q.Load.SourceUris) == 0 {
@ -120,7 +131,7 @@ func (l *Loader) Run(ctx context.Context) (*Job, error) {
func (l *Loader) newJob() (*bq.Job, io.Reader) {
config, media := l.LoadConfig.toBQ()
return &bq.Job{
JobReference: l.JobIDConfig.createJobRef(l.c.projectID),
JobReference: l.JobIDConfig.createJobRef(l.c),
Configuration: config,
}, media
}

View File

@ -74,25 +74,38 @@ func TestLoad(t *testing.T) {
c := &Client{projectID: "client-project-id"}
testCases := []struct {
dst *Table
src LoadSource
jobID string
config LoadConfig
want *bq.Job
dst *Table
src LoadSource
jobID string
location string
config LoadConfig
want *bq.Job
}{
{
dst: c.Dataset("dataset-id").Table("table-id"),
src: NewGCSReference("uri"),
want: defaultLoadJob(),
},
{
dst: c.Dataset("dataset-id").Table("table-id"),
src: NewGCSReference("uri"),
location: "loc",
want: func() *bq.Job {
j := defaultLoadJob()
j.JobReference.Location = "loc"
return j
}(),
},
{
dst: c.Dataset("dataset-id").Table("table-id"),
jobID: "ajob",
config: LoadConfig{
CreateDisposition: CreateNever,
WriteDisposition: WriteTruncate,
Labels: map[string]string{"a": "b"},
TimePartitioning: &TimePartitioning{Expiration: 1234 * time.Millisecond},
CreateDisposition: CreateNever,
WriteDisposition: WriteTruncate,
Labels: map[string]string{"a": "b"},
TimePartitioning: &TimePartitioning{Expiration: 1234 * time.Millisecond},
DestinationEncryptionConfig: &EncryptionConfig{KMSKeyName: "keyName"},
SchemaUpdateOptions: []string{"ALLOW_FIELD_ADDITION"},
},
src: NewGCSReference("uri"),
want: func() *bq.Job {
@ -104,10 +117,12 @@ func TestLoad(t *testing.T) {
Type: "DAY",
ExpirationMs: 1234,
}
j.Configuration.Load.DestinationEncryptionConfiguration = &bq.EncryptionConfiguration{KmsKeyName: "keyName"}
j.JobReference = &bq.JobReference{
JobId: "ajob",
ProjectId: "client-project-id",
}
j.Configuration.Load.SchemaUpdateOptions = []string{"ALLOW_FIELD_ADDITION"}
return j
}(),
},
@ -224,6 +239,7 @@ func TestLoad(t *testing.T) {
for i, tc := range testCases {
loader := tc.dst.LoaderFrom(tc.src)
loader.JobID = tc.jobID
loader.Location = tc.location
tc.config.Src = tc.src
tc.config.Dst = tc.dst
loader.LoadConfig = tc.config

View File

@ -15,9 +15,11 @@
package bigquery
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strconv"
"time"
"cloud.google.com/go/civil"
@ -134,6 +136,134 @@ func nulljson(valid bool, v interface{}) ([]byte, error) {
return json.Marshal(v)
}
func (n *NullInt64) UnmarshalJSON(b []byte) error {
n.Valid = false
n.Int64 = 0
if bytes.Equal(b, jsonNull) {
return nil
}
if err := json.Unmarshal(b, &n.Int64); err != nil {
return err
}
n.Valid = true
return nil
}
func (n *NullFloat64) UnmarshalJSON(b []byte) error {
n.Valid = false
n.Float64 = 0
if bytes.Equal(b, jsonNull) {
return nil
}
if err := json.Unmarshal(b, &n.Float64); err != nil {
return err
}
n.Valid = true
return nil
}
func (n *NullBool) UnmarshalJSON(b []byte) error {
n.Valid = false
n.Bool = false
if bytes.Equal(b, jsonNull) {
return nil
}
if err := json.Unmarshal(b, &n.Bool); err != nil {
return err
}
n.Valid = true
return nil
}
func (n *NullString) UnmarshalJSON(b []byte) error {
n.Valid = false
n.StringVal = ""
if bytes.Equal(b, jsonNull) {
return nil
}
if err := json.Unmarshal(b, &n.StringVal); err != nil {
return err
}
n.Valid = true
return nil
}
func (n *NullTimestamp) UnmarshalJSON(b []byte) error {
n.Valid = false
n.Timestamp = time.Time{}
if bytes.Equal(b, jsonNull) {
return nil
}
if err := json.Unmarshal(b, &n.Timestamp); err != nil {
return err
}
n.Valid = true
return nil
}
func (n *NullDate) UnmarshalJSON(b []byte) error {
n.Valid = false
n.Date = civil.Date{}
if bytes.Equal(b, jsonNull) {
return nil
}
if err := json.Unmarshal(b, &n.Date); err != nil {
return err
}
n.Valid = true
return nil
}
func (n *NullTime) UnmarshalJSON(b []byte) error {
n.Valid = false
n.Time = civil.Time{}
if bytes.Equal(b, jsonNull) {
return nil
}
s, err := strconv.Unquote(string(b))
if err != nil {
return err
}
t, err := civil.ParseTime(s)
if err != nil {
return err
}
n.Time = t
n.Valid = true
return nil
}
func (n *NullDateTime) UnmarshalJSON(b []byte) error {
n.Valid = false
n.DateTime = civil.DateTime{}
if bytes.Equal(b, jsonNull) {
return nil
}
s, err := strconv.Unquote(string(b))
if err != nil {
return err
}
dt, err := parseCivilDateTime(s)
if err != nil {
return err
}
n.DateTime = dt
n.Valid = true
return nil
}
var (
typeOfNullInt64 = reflect.TypeOf(NullInt64{})
typeOfNullFloat64 = reflect.TypeOf(NullFloat64{})

View File

@ -16,7 +16,16 @@ package bigquery
import (
"encoding/json"
"reflect"
"testing"
"cloud.google.com/go/civil"
"cloud.google.com/go/internal/testutil"
)
var (
nullsTestTime = civil.Time{Hour: 7, Minute: 50, Second: 22, Nanosecond: 1000}
nullsTestDateTime = civil.DateTime{Date: civil.Date{Year: 2016, Month: 11, Day: 5}, Time: nullsTestTime}
)
func TestNullsJSON(t *testing.T) {
@ -24,23 +33,23 @@ func TestNullsJSON(t *testing.T) {
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{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: nullsTestTime}, `"07:50:22.000001"`},
{&NullDateTime{Valid: true, DateTime: nullsTestDateTime}, `"2016-11-05 07:50:22.000001"`},
{NullInt64{}, `null`},
{NullFloat64{}, `null`},
{NullBool{}, `null`},
{NullString{}, `null`},
{NullTimestamp{}, `null`},
{NullDate{}, `null`},
{NullTime{}, `null`},
{NullDateTime{}, `null`},
{&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 {
@ -49,5 +58,16 @@ func TestNullsJSON(t *testing.T) {
if got, want := string(bytes), test.want; got != want {
t.Errorf("%#v: got %s, want %s", test.in, got, want)
}
typ := reflect.Indirect(reflect.ValueOf(test.in)).Type()
value := reflect.New(typ).Interface()
err = json.Unmarshal(bytes, value)
if err != nil {
t.Fatal(err)
}
if !testutil.Equal(value, test.in) {
t.Errorf("%#v: got %#v, want %#v", test.in, value, test.in)
}
}
}

View File

@ -20,7 +20,6 @@ import (
"fmt"
"reflect"
"regexp"
"strings"
"time"
"cloud.google.com/go/civil"
@ -205,6 +204,8 @@ func paramValue(v reflect.Value) (bq.QueryParameterValue, error) {
case typeOfTime:
// civil.Time has nanosecond resolution, but BigQuery TIME only microsecond.
// (If we send nanoseconds, then when we try to read the result we get "query job
// missing destination table").
res.Value = CivilTimeString(v.Interface().(civil.Time))
return res, nil
@ -306,11 +307,7 @@ func convertParamValue(qval *bq.QueryParameterValue, qtype *bq.QueryParameterTyp
case "TIMESTAMP":
return time.Parse(timestampFormat, qval.Value)
case "DATETIME":
parts := strings.Fields(qval.Value)
if len(parts) != 2 {
return nil, fmt.Errorf("bigquery: bad DATETIME value %q", qval.Value)
}
return civil.ParseDateTime(parts[0] + "T" + parts[1])
return parseCivilDateTime(qval.Value)
default:
return convertBasicType(qval.Value, paramTypeToFieldType[qtype.Type])
}

View File

@ -45,9 +45,9 @@ var scalarTests = []struct {
{time.Date(2016, 3, 20, 4, 22, 9, 5000, time.FixedZone("neg1-2", -3720)),
"2016-03-20 04:22:09.000005-01:02",
timestampParamType},
{civil.Date{2016, 3, 20}, "2016-03-20", dateParamType},
{civil.Time{4, 5, 6, 789000000}, "04:05:06.789000", timeParamType},
{civil.DateTime{civil.Date{2016, 3, 20}, civil.Time{4, 5, 6, 789000000}},
{civil.Date{Year: 2016, Month: 3, Day: 20}, "2016-03-20", dateParamType},
{civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 789000000}, "04:05:06.789000", timeParamType},
{civil.DateTime{Date: civil.Date{Year: 2016, Month: 3, Day: 20}, Time: civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 789000000}},
"2016-03-20 04:05:06.789000",
dateTimeParamType},
}

View File

@ -115,17 +115,21 @@ type QueryConfig struct {
// call LastStatus on the returned job to get statistics. Calling Status on a
// dry-run job will fail.
DryRun bool
// Custom encryption configuration (e.g., Cloud KMS keys).
DestinationEncryptionConfig *EncryptionConfig
}
func (qc *QueryConfig) toBQ() (*bq.JobConfiguration, error) {
qconf := &bq.JobConfigurationQuery{
Query: qc.Q,
CreateDisposition: string(qc.CreateDisposition),
WriteDisposition: string(qc.WriteDisposition),
AllowLargeResults: qc.AllowLargeResults,
Priority: string(qc.Priority),
MaximumBytesBilled: qc.MaxBytesBilled,
TimePartitioning: qc.TimePartitioning.toBQ(),
Query: qc.Q,
CreateDisposition: string(qc.CreateDisposition),
WriteDisposition: string(qc.WriteDisposition),
AllowLargeResults: qc.AllowLargeResults,
Priority: string(qc.Priority),
MaximumBytesBilled: qc.MaxBytesBilled,
TimePartitioning: qc.TimePartitioning.toBQ(),
DestinationEncryptionConfiguration: qc.DestinationEncryptionConfig.toBQ(),
}
if len(qc.TableDefinitions) > 0 {
qconf.TableDefinitions = make(map[string]bq.ExternalDataConfiguration)
@ -274,7 +278,7 @@ func (q *Query) newJob() (*bq.Job, error) {
return nil, err
}
return &bq.Job{
JobReference: q.JobIDConfig.createJobRef(q.client.projectID),
JobReference: q.JobIDConfig.createJobRef(q.client),
Configuration: config,
}, nil
}

View File

@ -120,9 +120,7 @@ func TestQuery(t *testing.T) {
g.MaxBadRecords = 1
g.Quote = "'"
g.SkipLeadingRows = 2
g.Schema = Schema([]*FieldSchema{
{Name: "name", Type: StringFieldType},
})
g.Schema = Schema{{Name: "name", Type: StringFieldType}}
return g
}(),
},
@ -352,6 +350,7 @@ func TestConfiguringQuery(t *testing.T) {
query.DefaultProjectID = "def-project-id"
query.DefaultDatasetID = "def-dataset-id"
query.TimePartitioning = &TimePartitioning{Expiration: 1234 * time.Second, Field: "f"}
query.DestinationEncryptionConfig = &EncryptionConfig{KMSKeyName: "keyName"}
// Note: Other configuration fields are tested in other tests above.
// A lot of that can be consolidated once Client.Copy is gone.
@ -363,9 +362,10 @@ func TestConfiguringQuery(t *testing.T) {
ProjectId: "def-project-id",
DatasetId: "def-dataset-id",
},
UseLegacySql: false,
TimePartitioning: &bq.TimePartitioning{ExpirationMs: 1234000, Field: "f", Type: "DAY"},
ForceSendFields: []string{"UseLegacySql"},
UseLegacySql: false,
TimePartitioning: &bq.TimePartitioning{ExpirationMs: 1234000, Field: "f", Type: "DAY"},
DestinationEncryptionConfiguration: &bq.EncryptionConfiguration{KmsKeyName: "keyName"},
ForceSendFields: []string{"UseLegacySql"},
},
},
JobReference: &bq.JobReference{

View File

@ -179,7 +179,7 @@ func TestSchemaConversion(t *testing.T) {
Name: "outer",
Required: true,
Type: "RECORD",
Schema: []*FieldSchema{
Schema: Schema{
{
Description: "inner field",
Name: "inner",

View File

@ -76,6 +76,9 @@ type TableMetadata struct {
// Information about a table stored outside of BigQuery.
ExternalDataConfig *ExternalDataConfig
// Custom encryption configuration (e.g., Cloud KMS keys).
EncryptionConfig *EncryptionConfig
// All the fields below are read-only.
FullID string // An opaque ID uniquely identifying the table.
@ -175,6 +178,32 @@ func bqToTimePartitioning(q *bq.TimePartitioning) *TimePartitioning {
}
}
// EncryptionConfig configures customer-managed encryption on tables.
type EncryptionConfig struct {
// Describes the Cloud KMS encryption key that will be used to protect
// destination BigQuery table. The BigQuery Service Account associated with your
// project requires access to this encryption key.
KMSKeyName string
}
func (e *EncryptionConfig) toBQ() *bq.EncryptionConfiguration {
if e == nil {
return nil
}
return &bq.EncryptionConfiguration{
KmsKeyName: e.KMSKeyName,
}
}
func bqToEncryptionConfig(q *bq.EncryptionConfiguration) *EncryptionConfig {
if q == nil {
return nil
}
return &EncryptionConfig{
KMSKeyName: q.KmsKeyName,
}
}
// StreamingBuffer holds information about the streaming buffer.
type StreamingBuffer struct {
// A lower-bound estimate of the number of bytes currently in the streaming
@ -265,6 +294,7 @@ func (tm *TableMetadata) toBQ() (*bq.Table, error) {
edc := tm.ExternalDataConfig.toBQ()
t.ExternalDataConfiguration = &edc
}
t.EncryptionConfiguration = tm.EncryptionConfig.toBQ()
if tm.FullID != "" {
return nil, errors.New("cannot set FullID on create")
}
@ -320,6 +350,7 @@ func bqToTableMetadata(t *bq.Table) (*TableMetadata, error) {
CreationTime: unixMillisToTime(t.CreationTime),
LastModifiedTime: unixMillisToTime(int64(t.LastModifiedTime)),
ETag: t.Etag,
EncryptionConfig: bqToEncryptionConfig(t.EncryptionConfiguration),
}
if t.Schema != nil {
md.Schema = bqToSchema(t.Schema)

View File

@ -53,6 +53,7 @@ func TestBQToTableMetadata(t *testing.T) {
Type: "DAY",
Field: "pfield",
},
EncryptionConfiguration: &bq.EncryptionConfiguration{KmsKeyName: "keyName"},
Type: "EXTERNAL",
View: &bq.ViewDefinition{Query: "view-query"},
Labels: map[string]string{"a": "b"},
@ -82,7 +83,8 @@ func TestBQToTableMetadata(t *testing.T) {
EstimatedRows: 3,
OldestEntryTime: aTime,
},
ETag: "etag",
EncryptionConfig: &EncryptionConfig{KMSKeyName: "keyName"},
ETag: "etag",
},
},
} {
@ -115,6 +117,7 @@ func TestTableMetadataToBQ(t *testing.T) {
ExpirationTime: aTime,
Labels: map[string]string{"a": "b"},
ExternalDataConfig: &ExternalDataConfig{SourceFormat: Bigtable},
EncryptionConfig: &EncryptionConfig{KMSKeyName: "keyName"},
},
&bq.Table{
FriendlyName: "n",
@ -127,6 +130,7 @@ func TestTableMetadataToBQ(t *testing.T) {
ExpirationTime: aTimeMillis,
Labels: map[string]string{"a": "b"},
ExternalDataConfiguration: &bq.ExternalDataConfiguration{SourceFormat: "BIGTABLE"},
EncryptionConfiguration: &bq.EncryptionConfiguration{KmsKeyName: "keyName"},
},
},
{

View File

@ -21,6 +21,7 @@ import (
"math"
"reflect"
"strconv"
"strings"
"time"
"cloud.google.com/go/civil"
@ -540,6 +541,7 @@ func valuesToMap(vs []Value, schema Schema) (map[string]Value, error) {
type StructSaver struct {
// Schema determines what fields of the struct are uploaded. It should
// match the table's schema.
// Schema is optional for StructSavers that are passed to Uploader.Put.
Schema Schema
// If non-empty, BigQuery will use InsertID to de-duplicate insertions
@ -707,6 +709,16 @@ func CivilDateTimeString(dt civil.DateTime) string {
return dt.Date.String() + " " + CivilTimeString(dt.Time)
}
// parseCivilDateTime parses a date-time represented in a BigQuery SQL
// compatible format and returns a civil.DateTime.
func parseCivilDateTime(s string) (civil.DateTime, error) {
parts := strings.Fields(s)
if len(parts) != 2 {
return civil.DateTime{}, fmt.Errorf("bigquery: bad DATETIME value %q", s)
}
return civil.ParseDateTime(parts[0] + "T" + parts[1])
}
// convertRows converts a series of TableRows into a series of Value slices.
// schema is used to interpret the data from rows; its length must match the
// length of each row.

View File

@ -30,7 +30,7 @@ import (
)
func TestConvertBasicValues(t *testing.T) {
schema := []*FieldSchema{
schema := Schema{
{Type: StringFieldType},
{Type: IntegerFieldType},
{Type: FloatFieldType},
@ -57,7 +57,7 @@ func TestConvertBasicValues(t *testing.T) {
}
func TestConvertTime(t *testing.T) {
schema := []*FieldSchema{
schema := Schema{
{Type: TimestampFieldType},
{Type: DateFieldType},
{Type: TimeFieldType},
@ -103,9 +103,7 @@ func TestConvertSmallTimes(t *testing.T) {
}
func TestConvertNullValues(t *testing.T) {
schema := []*FieldSchema{
{Type: StringFieldType},
}
schema := Schema{{Type: StringFieldType}}
row := &bq.TableRow{
F: []*bq.TableCell{
{V: nil},
@ -122,7 +120,7 @@ func TestConvertNullValues(t *testing.T) {
}
func TestBasicRepetition(t *testing.T) {
schema := []*FieldSchema{
schema := Schema{
{Type: IntegerFieldType, Repeated: true},
}
row := &bq.TableRow{
@ -153,7 +151,7 @@ func TestBasicRepetition(t *testing.T) {
}
func TestNestedRecordContainingRepetition(t *testing.T) {
schema := []*FieldSchema{
schema := Schema{
{
Type: RecordFieldType,
Schema: Schema{
@ -190,7 +188,7 @@ func TestNestedRecordContainingRepetition(t *testing.T) {
}
func TestRepeatedRecordContainingRepetition(t *testing.T) {
schema := []*FieldSchema{
schema := Schema{
{
Type: RecordFieldType,
Repeated: true,
@ -264,7 +262,7 @@ func TestRepeatedRecordContainingRepetition(t *testing.T) {
}
func TestRepeatedRecordContainingRecord(t *testing.T) {
schema := []*FieldSchema{
schema := Schema{
{
Type: RecordFieldType,
Repeated: true,
@ -399,14 +397,17 @@ func TestValuesSaverConvertsToMap(t *testing.T) {
}{
{
vs: ValuesSaver{
Schema: []*FieldSchema{
Schema: Schema{
{Name: "intField", Type: IntegerFieldType},
{Name: "strField", Type: StringFieldType},
{Name: "dtField", Type: DateTimeFieldType},
},
InsertID: "iid",
Row: []Value{1, "a",
civil.DateTime{civil.Date{1, 2, 3}, civil.Time{4, 5, 6, 7000}}},
civil.DateTime{
Date: civil.Date{Year: 1, Month: 2, Day: 3},
Time: civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 7000}},
},
},
wantInsertID: "iid",
wantRow: map[string]Value{"intField": 1, "strField": "a",
@ -414,12 +415,12 @@ func TestValuesSaverConvertsToMap(t *testing.T) {
},
{
vs: ValuesSaver{
Schema: []*FieldSchema{
Schema: Schema{
{Name: "intField", Type: IntegerFieldType},
{
Name: "recordField",
Type: RecordFieldType,
Schema: []*FieldSchema{
Schema: Schema{
{Name: "nestedInt", Type: IntegerFieldType, Repeated: true},
},
},
@ -559,8 +560,8 @@ func TestStructSaver(t *testing.T) {
}
}
ct1 := civil.Time{1, 2, 3, 4000}
ct2 := civil.Time{5, 6, 7, 8000}
ct1 := civil.Time{Hour: 1, Minute: 2, Second: 3, Nanosecond: 4000}
ct2 := civil.Time{Hour: 5, Minute: 6, Second: 7, Nanosecond: 8000}
in := T{
S: "x",
R: []int{1, 2},
@ -629,7 +630,7 @@ func TestStructSaverErrors(t *testing.T) {
}
func TestConvertRows(t *testing.T) {
schema := []*FieldSchema{
schema := Schema{
{Type: StringFieldType},
{Type: IntegerFieldType},
{Type: FloatFieldType},
@ -772,9 +773,9 @@ var (
}
testTimestamp = time.Date(2016, 11, 5, 7, 50, 22, 8, time.UTC)
testDate = civil.Date{2016, 11, 5}
testTime = civil.Time{7, 50, 22, 8}
testDateTime = civil.DateTime{testDate, testTime}
testDate = civil.Date{Year: 2016, Month: 11, Day: 5}
testTime = civil.Time{Hour: 7, Minute: 50, Second: 22, Nanosecond: 8}
testDateTime = civil.DateTime{Date: testDate, Time: testTime}
testValues = []Value{"x", "y", []byte{1, 2, 3}, int64(7), int64(8), 3.14, true,
testTimestamp, testDate, testTime, testDateTime,
@ -1069,7 +1070,7 @@ func TestStructLoaderErrors(t *testing.T) {
t.Errorf("%T: got nil, want error", bad6)
}
// sl.set's error is sticky, with even good input.
// sl.set's error is sticky, even with good input.
err2 := sl.set(&repStruct{}, repSchema)
if err2 != err {
t.Errorf("%v != %v, expected equal", err2, err)
@ -1087,6 +1088,7 @@ func TestStructLoaderErrors(t *testing.T) {
{Name: "b", Type: BooleanFieldType},
{Name: "s", Type: StringFieldType},
{Name: "d", Type: DateFieldType},
{Name: "r", Type: RecordFieldType, Schema: Schema{{Name: "X", Type: IntegerFieldType}}},
}
type s struct {
I int

View File

@ -106,10 +106,17 @@ func (ac *AdminClient) Tables(ctx context.Context) ([]string, error) {
req := &btapb.ListTablesRequest{
Parent: prefix,
}
res, err := ac.tClient.ListTables(ctx, req)
var res *btapb.ListTablesResponse
err := gax.Invoke(ctx, func(ctx context.Context) error {
var err error
res, err = ac.tClient.ListTables(ctx, req)
return err
}, retryOptions...)
if err != nil {
return nil, err
}
names := make([]string, 0, len(res.Tables))
for _, tbl := range res.Tables {
names = append(names, strings.TrimPrefix(tbl.Name, prefix+"/tables/"))
@ -145,13 +152,13 @@ func (ac *AdminClient) CreateTableFromConf(ctx context.Context, conf *TableConf)
ctx = mergeOutgoingMetadata(ctx, ac.md)
var req_splits []*btapb.CreateTableRequest_Split
for _, split := range conf.SplitKeys {
req_splits = append(req_splits, &btapb.CreateTableRequest_Split{[]byte(split)})
req_splits = append(req_splits, &btapb.CreateTableRequest_Split{Key: []byte(split)})
}
var tbl btapb.Table
if conf.Families != nil {
tbl.ColumnFamilies = make(map[string]*btapb.ColumnFamily)
for fam, policy := range conf.Families {
tbl.ColumnFamilies[fam] = &btapb.ColumnFamily{policy.proto()}
tbl.ColumnFamilies[fam] = &btapb.ColumnFamily{GcRule: policy.proto()}
}
}
prefix := ac.instancePrefix()
@ -174,7 +181,7 @@ func (ac *AdminClient) CreateColumnFamily(ctx context.Context, table, family str
Name: prefix + "/tables/" + table,
Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{
Id: family,
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{&btapb.ColumnFamily{}},
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{Create: &btapb.ColumnFamily{}},
}},
}
_, err := ac.tClient.ModifyColumnFamilies(ctx, req)
@ -200,7 +207,7 @@ func (ac *AdminClient) DeleteColumnFamily(ctx context.Context, table, family str
Name: prefix + "/tables/" + table,
Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{
Id: family,
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Drop{true},
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Drop{Drop: true},
}},
}
_, err := ac.tClient.ModifyColumnFamilies(ctx, req)
@ -227,10 +234,18 @@ func (ac *AdminClient) TableInfo(ctx context.Context, table string) (*TableInfo,
req := &btapb.GetTableRequest{
Name: prefix + "/tables/" + table,
}
res, err := ac.tClient.GetTable(ctx, req)
var res *btapb.Table
err := gax.Invoke(ctx, func(ctx context.Context) error {
var err error
res, err = ac.tClient.GetTable(ctx, req)
return err
}, retryOptions...)
if err != nil {
return nil, err
}
ti := &TableInfo{}
for name, fam := range res.ColumnFamilies {
ti.Families = append(ti.Families, name)
@ -249,7 +264,7 @@ func (ac *AdminClient) SetGCPolicy(ctx context.Context, table, family string, po
Name: prefix + "/tables/" + table,
Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{
Id: family,
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Update{&btapb.ColumnFamily{GcRule: policy.proto()}},
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Update{Update: &btapb.ColumnFamily{GcRule: policy.proto()}},
}},
}
_, err := ac.tClient.ModifyColumnFamilies(ctx, req)
@ -262,7 +277,7 @@ func (ac *AdminClient) DropRowRange(ctx context.Context, table, rowKeyPrefix str
prefix := ac.instancePrefix()
req := &btapb.DropRowRangeRequest{
Name: prefix + "/tables/" + table,
Target: &btapb.DropRowRangeRequest_RowKeyPrefix{[]byte(rowKeyPrefix)},
Target: &btapb.DropRowRangeRequest_RowKeyPrefix{RowKeyPrefix: []byte(rowKeyPrefix)},
}
_, err := ac.tClient.DropRowRange(ctx, req)
return err
@ -697,7 +712,7 @@ func (iac *InstanceAdminClient) CreateInstanceWithClusters(ctx context.Context,
// DeleteInstance deletes an instance from the project.
func (iac *InstanceAdminClient) DeleteInstance(ctx context.Context, instanceId string) error {
ctx = mergeOutgoingMetadata(ctx, iac.md)
req := &btapb.DeleteInstanceRequest{"projects/" + iac.project + "/instances/" + instanceId}
req := &btapb.DeleteInstanceRequest{Name: "projects/" + iac.project + "/instances/" + instanceId}
_, err := iac.iClient.DeleteInstance(ctx, req)
return err
}
@ -808,7 +823,7 @@ func (iac *InstanceAdminClient) CreateCluster(ctx context.Context, conf *Cluster
// production use. It is not subject to any SLA or deprecation policy.
func (iac *InstanceAdminClient) DeleteCluster(ctx context.Context, instanceId, clusterId string) error {
ctx = mergeOutgoingMetadata(ctx, iac.md)
req := &btapb.DeleteClusterRequest{"projects/" + iac.project + "/instances/" + instanceId + "/clusters/" + clusterId}
req := &btapb.DeleteClusterRequest{Name: "projects/" + iac.project + "/instances/" + instanceId + "/clusters/" + clusterId}
_, err := iac.iClient.DeleteCluster(ctx, req)
return err
}
@ -848,3 +863,23 @@ func (iac *InstanceAdminClient) Clusters(ctx context.Context, instanceId string)
}
return cis, nil
}
// GetCluster fetches a cluster in an instance
func (iac *InstanceAdminClient) GetCluster(ctx context.Context, instanceID, clusterID string) (*ClusterInfo, error) {
ctx = mergeOutgoingMetadata(ctx, iac.md)
req := &btapb.GetClusterRequest{Name: "projects/" + iac.project + "/instances/" + instanceID + "/clusters" + clusterID}
c, err := iac.iClient.GetCluster(ctx, req)
if err != nil {
return nil, err
}
nameParts := strings.Split(c.Name, "/")
locParts := strings.Split(c.Location, "/")
cis := &ClusterInfo{
Name: nameParts[len(nameParts)-1],
Zone: locParts[len(locParts)-1],
ServeNodes: int(c.ServeNodes),
State: c.State.String(),
}
return cis, nil
}

View File

@ -25,6 +25,7 @@ import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
btapb "google.golang.org/genproto/googleapis/bigtable/admin/v2"
"strings"
)
@ -183,6 +184,67 @@ func TestAdminIntegration(t *testing.T) {
}
}
func TestInstanceUpdate(t *testing.T) {
testEnv, err := NewIntegrationEnv()
if err != nil {
t.Fatalf("IntegrationEnv: %v", err)
}
defer testEnv.Close()
timeout := 2 * time.Second
if testEnv.Config().UseProd {
timeout = 5 * time.Minute
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
adminClient, err := testEnv.NewAdminClient()
if err != nil {
t.Fatalf("NewAdminClient: %v", err)
}
defer adminClient.Close()
iAdminClient, err := testEnv.NewInstanceAdminClient()
if err != nil {
t.Fatalf("NewInstanceAdminClient: %v", err)
}
if iAdminClient == nil {
return
}
defer iAdminClient.Close()
iInfo, err := iAdminClient.InstanceInfo(ctx, adminClient.instance)
if err != nil {
t.Errorf("InstanceInfo: %v", err)
}
if iInfo.Name != adminClient.instance {
t.Errorf("InstanceInfo returned name %#v, want %#v", iInfo.Name, adminClient.instance)
}
if iInfo.DisplayName != adminClient.instance {
t.Errorf("InstanceInfo returned name %#v, want %#v", iInfo.Name, adminClient.instance)
}
const numNodes = 4
// update cluster nodes
if err := iAdminClient.UpdateCluster(ctx, adminClient.instance, testEnv.Config().Cluster, int32(numNodes)); err != nil {
t.Errorf("UpdateCluster: %v", err)
}
// get cluster after updating
cis, err := iAdminClient.GetCluster(ctx, adminClient.instance, testEnv.Config().Cluster)
if err != nil {
t.Errorf("GetCluster %v", err)
}
if cis.ServeNodes != int(numNodes) {
t.Errorf("ServeNodes returned %d, want %d", cis.ServeNodes, int(numNodes))
}
}
func TestAdminSnapshotIntegration(t *testing.T) {
testEnv, err := NewIntegrationEnv()
if err != nil {
@ -299,3 +361,73 @@ func TestAdminSnapshotIntegration(t *testing.T) {
t.Fatalf("List after delete len: %d, want: %d", got, want)
}
}
func TestGranularity(t *testing.T) {
testEnv, err := NewIntegrationEnv()
if err != nil {
t.Fatalf("IntegrationEnv: %v", err)
}
defer testEnv.Close()
timeout := 2 * time.Second
if testEnv.Config().UseProd {
timeout = 5 * time.Minute
}
ctx, _ := context.WithTimeout(context.Background(), timeout)
adminClient, err := testEnv.NewAdminClient()
if err != nil {
t.Fatalf("NewAdminClient: %v", err)
}
defer adminClient.Close()
list := func() []string {
tbls, err := adminClient.Tables(ctx)
if err != nil {
t.Fatalf("Fetching list of tables: %v", err)
}
sort.Strings(tbls)
return tbls
}
containsAll := func(got, want []string) bool {
gotSet := make(map[string]bool)
for _, s := range got {
gotSet[s] = true
}
for _, s := range want {
if !gotSet[s] {
return false
}
}
return true
}
defer adminClient.DeleteTable(ctx, "mytable")
if err := adminClient.CreateTable(ctx, "mytable"); err != nil {
t.Fatalf("Creating table: %v", err)
}
tables := list()
if got, want := tables, []string{"mytable"}; !containsAll(got, want) {
t.Errorf("adminClient.Tables returned %#v, want %#v", got, want)
}
// calling ModifyColumnFamilies to check the granularity of table
prefix := adminClient.instancePrefix()
req := &btapb.ModifyColumnFamiliesRequest{
Name: prefix + "/tables/" + "mytable",
Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{
Id: "cf",
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{&btapb.ColumnFamily{}},
}},
}
table, err := adminClient.tClient.ModifyColumnFamilies(ctx, req)
if err != nil {
t.Fatalf("Creating column family: %v", err)
}
if table.Granularity != btapb.Table_TimestampGranularity(btapb.Table_MILLIS) {
t.Errorf("ModifyColumnFamilies returned granularity %#v, want %#v", table.Granularity, btapb.Table_TimestampGranularity(btapb.Table_MILLIS))
}
}

View File

@ -33,6 +33,7 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
const prodAddr = "bigtable.googleapis.com:443"
@ -83,11 +84,13 @@ func NewClientWithConfig(ctx context.Context, project, instance string, config C
if err != nil {
return nil, fmt.Errorf("dialing: %v", err)
}
return &Client{
conn: conn,
client: btpb.NewBigtableClient(conn),
project: project,
instance: instance,
conn: conn,
client: btpb.NewBigtableClient(conn),
project: project,
instance: instance,
appProfile: config.AppProfile,
}, nil
}
@ -147,7 +150,11 @@ func (t *Table) ReadRows(ctx context.Context, arg RowSet, f func(Row) bool, opts
ctx = mergeOutgoingMetadata(ctx, t.md)
var prevRowKey string
err := gax.Invoke(ctx, func(ctx context.Context) error {
var err error
ctx = traceStartSpan(ctx, "cloud.google.com/go/bigtable.ReadRows")
defer func() { traceEndSpan(ctx, err) }()
attrMap := make(map[string]interface{})
err = gax.Invoke(ctx, func(ctx context.Context) error {
if !arg.valid() {
// Empty row set, no need to make an API call.
// NOTE: we must return early if arg == RowList{} because reading
@ -165,6 +172,7 @@ func (t *Table) ReadRows(ctx context.Context, arg RowSet, f func(Row) bool, opts
ctx, cancel := context.WithCancel(ctx) // for aborting the stream
defer cancel()
startTime := time.Now()
stream, err := t.c.client.ReadRows(ctx, req)
if err != nil {
return err
@ -178,6 +186,10 @@ func (t *Table) ReadRows(ctx context.Context, arg RowSet, f func(Row) bool, opts
if err != nil {
// Reset arg for next Invoke call.
arg = arg.retainRowsAfter(prevRowKey)
attrMap["rowKey"] = prevRowKey
attrMap["error"] = err.Error()
attrMap["time_secs"] = time.Since(startTime).Seconds()
tracePrintf(ctx, attrMap, "Retry details in ReadRows")
return err
}
@ -317,10 +329,10 @@ func (r RowRange) String() string {
func (r RowRange) proto() *btpb.RowSet {
rr := &btpb.RowRange{
StartKey: &btpb.RowRange_StartKeyClosed{[]byte(r.start)},
StartKey: &btpb.RowRange_StartKeyClosed{StartKeyClosed: []byte(r.start)},
}
if !r.Unbounded() {
rr.EndKey = &btpb.RowRange_EndKeyOpen{[]byte(r.limit)}
rr.EndKey = &btpb.RowRange_EndKeyOpen{EndKeyOpen: []byte(r.limit)}
}
return &btpb.RowSet{RowRanges: []*btpb.RowRange{rr}}
}
@ -462,6 +474,9 @@ func (t *Table) Apply(ctx context.Context, row string, m *Mutation, opts ...Appl
}
}
var err error
ctx = traceStartSpan(ctx, "cloud.google.com/go/bigtable/Apply")
defer func() { traceEndSpan(ctx, err) }()
var callOptions []gax.CallOption
if m.cond == nil {
req := &btpb.MutateRowRequest{
@ -507,7 +522,7 @@ func (t *Table) Apply(ctx context.Context, row string, m *Mutation, opts ...Appl
callOptions = retryOptions
}
var cmRes *btpb.CheckAndMutateRowResponse
err := gax.Invoke(ctx, func(ctx context.Context) error {
err = gax.Invoke(ctx, func(ctx context.Context) error {
var err error
cmRes, err = t.c.client.CheckAndMutateRow(ctx, req)
return err
@ -564,7 +579,7 @@ func NewCondMutation(cond Filter, mtrue, mfalse *Mutation) *Mutation {
// The timestamp will be truncated to millisecond granularity.
// A timestamp of ServerTime means to use the server timestamp.
func (m *Mutation) Set(family, column string, ts Timestamp, value []byte) {
m.ops = append(m.ops, &btpb.Mutation{Mutation: &btpb.Mutation_SetCell_{&btpb.Mutation_SetCell{
m.ops = append(m.ops, &btpb.Mutation{Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{
FamilyName: family,
ColumnQualifier: []byte(column),
TimestampMicros: int64(ts.TruncateToMilliseconds()),
@ -574,7 +589,7 @@ func (m *Mutation) Set(family, column string, ts Timestamp, value []byte) {
// DeleteCellsInColumn will delete all the cells whose columns are family:column.
func (m *Mutation) DeleteCellsInColumn(family, column string) {
m.ops = append(m.ops, &btpb.Mutation{Mutation: &btpb.Mutation_DeleteFromColumn_{&btpb.Mutation_DeleteFromColumn{
m.ops = append(m.ops, &btpb.Mutation{Mutation: &btpb.Mutation_DeleteFromColumn_{DeleteFromColumn: &btpb.Mutation_DeleteFromColumn{
FamilyName: family,
ColumnQualifier: []byte(column),
}}})
@ -585,7 +600,7 @@ func (m *Mutation) DeleteCellsInColumn(family, column string) {
// If end is zero, it will be interpreted as infinity.
// The timestamps will be truncated to millisecond granularity.
func (m *Mutation) DeleteTimestampRange(family, column string, start, end Timestamp) {
m.ops = append(m.ops, &btpb.Mutation{Mutation: &btpb.Mutation_DeleteFromColumn_{&btpb.Mutation_DeleteFromColumn{
m.ops = append(m.ops, &btpb.Mutation{Mutation: &btpb.Mutation_DeleteFromColumn_{DeleteFromColumn: &btpb.Mutation_DeleteFromColumn{
FamilyName: family,
ColumnQualifier: []byte(column),
TimeRange: &btpb.TimestampRange{
@ -597,14 +612,14 @@ func (m *Mutation) DeleteTimestampRange(family, column string, start, end Timest
// DeleteCellsInFamily will delete all the cells whose columns are family:*.
func (m *Mutation) DeleteCellsInFamily(family string) {
m.ops = append(m.ops, &btpb.Mutation{Mutation: &btpb.Mutation_DeleteFromFamily_{&btpb.Mutation_DeleteFromFamily{
m.ops = append(m.ops, &btpb.Mutation{Mutation: &btpb.Mutation_DeleteFromFamily_{DeleteFromFamily: &btpb.Mutation_DeleteFromFamily{
FamilyName: family,
}}})
}
// DeleteRow deletes the entire row.
func (m *Mutation) DeleteRow() {
m.ops = append(m.ops, &btpb.Mutation{Mutation: &btpb.Mutation_DeleteFromRow_{&btpb.Mutation_DeleteFromRow{}}})
m.ops = append(m.ops, &btpb.Mutation{Mutation: &btpb.Mutation_DeleteFromRow_{DeleteFromRow: &btpb.Mutation_DeleteFromRow{}}})
}
// entryErr is a container that combines an entry with the error that was returned for it.
@ -642,7 +657,13 @@ func (t *Table) ApplyBulk(ctx context.Context, rowKeys []string, muts []*Mutatio
// entries will be reduced after each invocation to just what needs to be retried.
entries := make([]*entryErr, len(rowKeys))
copy(entries, origEntries)
err := gax.Invoke(ctx, func(ctx context.Context) error {
var err error
ctx = traceStartSpan(ctx, "cloud.google.com/go/bigtable/ApplyBulk")
defer func() { traceEndSpan(ctx, err) }()
attrMap := make(map[string]interface{})
err = gax.Invoke(ctx, func(ctx context.Context) error {
attrMap["rowCount"] = len(entries)
tracePrintf(ctx, attrMap, "Row count in ApplyBulk")
err := t.doApplyBulk(ctx, entries, opts...)
if err != nil {
// We want to retry the entire request with the current entries
@ -652,11 +673,10 @@ func (t *Table) ApplyBulk(ctx context.Context, rowKeys []string, muts []*Mutatio
if len(entries) > 0 && len(idempotentRetryCodes) > 0 {
// We have at least one mutation that needs to be retried.
// Return an arbitrary error that is retryable according to callOptions.
return grpc.Errorf(idempotentRetryCodes[0], "Synthetic error: partial failure of ApplyBulk")
return status.Errorf(idempotentRetryCodes[0], "Synthetic error: partial failure of ApplyBulk")
}
return nil
}, retryOptions...)
if err != nil {
return nil, err
}
@ -721,11 +741,11 @@ func (t *Table) doApplyBulk(ctx context.Context, entryErrs []*entryErr, opts ...
}
for i, entry := range res.Entries {
status := entry.Status
if status.Code == int32(codes.OK) {
s := entry.Status
if s.Code == int32(codes.OK) {
entryErrs[i].Err = nil
} else {
entryErrs[i].Err = grpc.Errorf(codes.Code(status.Code), status.Message)
entryErrs[i].Err = status.Errorf(codes.Code(s.Code), s.Message)
}
}
after(res)
@ -803,7 +823,7 @@ func (m *ReadModifyWrite) AppendValue(family, column string, v []byte) {
m.ops = append(m.ops, &btpb.ReadModifyWriteRule{
FamilyName: family,
ColumnQualifier: []byte(column),
Rule: &btpb.ReadModifyWriteRule_AppendValue{v},
Rule: &btpb.ReadModifyWriteRule_AppendValue{AppendValue: v},
})
}
@ -815,7 +835,7 @@ func (m *ReadModifyWrite) Increment(family, column string, delta int64) {
m.ops = append(m.ops, &btpb.ReadModifyWriteRule{
FamilyName: family,
ColumnQualifier: []byte(column),
Rule: &btpb.ReadModifyWriteRule_IncrementAmount{delta},
Rule: &btpb.ReadModifyWriteRule_IncrementAmount{IncrementAmount: delta},
})
}
@ -825,3 +845,40 @@ func mergeOutgoingMetadata(ctx context.Context, md metadata.MD) context.Context
mdCopy, _ := metadata.FromOutgoingContext(ctx)
return metadata.NewOutgoingContext(ctx, metadata.Join(mdCopy, md))
}
func (t *Table) SampleRowKeys(ctx context.Context) ([]string, error) {
ctx = mergeOutgoingMetadata(ctx, t.md)
var sampledRowKeys []string
err := gax.Invoke(ctx, func(ctx context.Context) error {
sampledRowKeys = nil
req := &btpb.SampleRowKeysRequest{
TableName: t.c.fullTableName(t.table),
AppProfileId: t.c.appProfile,
}
ctx, cancel := context.WithCancel(ctx) // for aborting the stream
defer cancel()
stream, err := t.c.client.SampleRowKeys(ctx, req)
if err != nil {
return err
}
for {
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
key := string(res.RowKey)
if key == "" {
continue
}
sampledRowKeys = append(sampledRowKeys, key)
}
return nil
}, retryOptions...)
return sampledRowKeys, err
}

View File

@ -154,10 +154,11 @@ func TestClientIntegration(t *testing.T) {
}
checkpoint("inserted initial data")
if err := adminClient.WaitForReplication(ctx, table); err != nil {
t.Errorf("Waiting for replication for table %q: %v", table, err)
}
checkpoint("waited for replication")
// TODO(igorbernstein): re-enable this when ready
//if err := adminClient.WaitForReplication(ctx, table); err != nil {
// t.Errorf("Waiting for replication for table %q: %v", table, err)
//}
//checkpoint("waited for replication")
// Do a conditional mutation with a complex filter.
mutTrue := NewMutation()
@ -1062,3 +1063,101 @@ func clearTimestamps(r Row) {
}
}
}
func TestSampleRowKeys(t *testing.T) {
start := time.Now()
lastCheckpoint := start
checkpoint := func(s string) {
n := time.Now()
t.Logf("[%s] %v since start, %v since last checkpoint", s, n.Sub(start), n.Sub(lastCheckpoint))
lastCheckpoint = n
}
ctx := context.Background()
client, adminClient, table, err := doSetup(ctx)
if err != nil {
t.Fatalf("%v", err)
}
defer client.Close()
defer adminClient.Close()
tbl := client.Open(table)
// Delete the table at the end of the test.
// Do this even before creating the table so that if this is running
// against production and CreateTable fails there's a chance of cleaning it up.
defer adminClient.DeleteTable(ctx, table)
// Insert some data.
initialData := map[string][]string{
"wmckinley11": {"tjefferson11"},
"gwashington77": {"jadams77"},
"tjefferson0": {"gwashington0", "jadams0"},
}
for row, ss := range initialData {
mut := NewMutation()
for _, name := range ss {
mut.Set("follows", name, 0, []byte("1"))
}
if err := tbl.Apply(ctx, row, mut); err != nil {
t.Errorf("Mutating row %q: %v", row, err)
}
}
checkpoint("inserted initial data")
sampleKeys, err := tbl.SampleRowKeys(context.Background())
if err != nil {
t.Errorf("%s: %v", "SampleRowKeys:", err)
}
if len(sampleKeys) == 0 {
t.Error("SampleRowKeys length 0")
}
checkpoint("tested SampleRowKeys.")
}
func doSetup(ctx context.Context) (*Client, *AdminClient, string, error) {
start := time.Now()
lastCheckpoint := start
checkpoint := func(s string) {
n := time.Now()
fmt.Printf("[%s] %v since start, %v since last checkpoint", s, n.Sub(start), n.Sub(lastCheckpoint))
lastCheckpoint = n
}
testEnv, err := NewIntegrationEnv()
if err != nil {
return nil, nil, "", fmt.Errorf("IntegrationEnv: %v", err)
}
var timeout time.Duration
if testEnv.Config().UseProd {
timeout = 10 * time.Minute
fmt.Printf("Running test against production")
} else {
timeout = 1 * time.Minute
fmt.Printf("bttest.Server running on %s", testEnv.Config().AdminEndpoint)
}
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
client, err := testEnv.NewClient()
if err != nil {
return nil, nil, "", fmt.Errorf("Client: %v", err)
}
checkpoint("dialed Client")
adminClient, err := testEnv.NewAdminClient()
if err != nil {
return nil, nil, "", fmt.Errorf("AdminClient: %v", err)
}
checkpoint("dialed AdminClient")
table := testEnv.Config().Table
if err := adminClient.CreateTable(ctx, table); err != nil {
return nil, nil, "", fmt.Errorf("Creating table: %v", err)
}
checkpoint("created table")
if err := adminClient.CreateColumnFamily(ctx, table, "follows"); err != nil {
return nil, nil, "", fmt.Errorf("Creating column family: %v", err)
}
checkpoint(`created "follows" column family`)
return client, adminClient, table, nil
}

View File

@ -121,7 +121,7 @@ func (s *server) CreateTable(ctx context.Context, req *btapb.CreateTableRequest)
s.mu.Lock()
if _, ok := s.tables[tbl]; ok {
s.mu.Unlock()
return nil, grpc.Errorf(codes.AlreadyExists, "table %q already exists", tbl)
return nil, status.Errorf(codes.AlreadyExists, "table %q already exists", tbl)
}
s.tables[tbl] = newTable(req)
s.mu.Unlock()
@ -151,7 +151,7 @@ func (s *server) GetTable(ctx context.Context, req *btapb.GetTableRequest) (*bta
tblIns, ok := s.tables[tbl]
s.mu.Unlock()
if !ok {
return nil, grpc.Errorf(codes.NotFound, "table %q not found", tbl)
return nil, status.Errorf(codes.NotFound, "table %q not found", tbl)
}
return &btapb.Table{
@ -177,7 +177,7 @@ func (s *server) ModifyColumnFamilies(ctx context.Context, req *btapb.ModifyColu
tbl, ok := s.tables[req.Name]
s.mu.Unlock()
if !ok {
return nil, grpc.Errorf(codes.NotFound, "table %q not found", req.Name)
return nil, status.Errorf(codes.NotFound, "table %q not found", req.Name)
}
tbl.mu.Lock()
@ -186,7 +186,7 @@ func (s *server) ModifyColumnFamilies(ctx context.Context, req *btapb.ModifyColu
for _, mod := range req.Modifications {
if create := mod.GetCreate(); create != nil {
if _, ok := tbl.families[mod.Id]; ok {
return nil, grpc.Errorf(codes.AlreadyExists, "family %q already exists", mod.Id)
return nil, status.Errorf(codes.AlreadyExists, "family %q already exists", mod.Id)
}
newcf := &columnFamily{
name: req.Name + "/columnFamilies/" + mod.Id,
@ -218,6 +218,7 @@ func (s *server) ModifyColumnFamilies(ctx context.Context, req *btapb.ModifyColu
return &btapb.Table{
Name: tblName,
ColumnFamilies: toColumnFamilies(tbl.families),
Granularity: btapb.Table_TimestampGranularity(btapb.Table_MILLIS),
}, nil
}
@ -415,7 +416,7 @@ func streamRow(stream btpb.Bigtable_ReadRowsServer, r *row, f *btpb.RowFilter) (
// We can't have a cell with just COMMIT set, which would imply a new empty cell.
// So modify the last cell to have the COMMIT flag set.
if len(rrr.Chunks) > 0 {
rrr.Chunks[len(rrr.Chunks)-1].RowStatus = &btpb.ReadRowsResponse_CellChunk_CommitRow{true}
rrr.Chunks[len(rrr.Chunks)-1].RowStatus = &btpb.ReadRowsResponse_CellChunk_CommitRow{CommitRow: true}
}
return true, stream.Send(rrr)
@ -429,6 +430,10 @@ func filterRow(f *btpb.RowFilter, r *row) bool {
}
// Handle filters that apply beyond just including/excluding cells.
switch f := f.Filter.(type) {
case *btpb.RowFilter_BlockAllFilter:
return !f.BlockAllFilter
case *btpb.RowFilter_PassAllFilter:
return f.PassAllFilter
case *btpb.RowFilter_Chain_:
for _, sub := range f.Chain.Filters {
if !filterRow(sub, r) {

View File

@ -46,7 +46,7 @@ func TestConcurrentMutationsReadModifyAndGC(t *testing.T) {
Name: name,
Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{
Id: "cf",
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{&btapb.ColumnFamily{}},
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{Create: &btapb.ColumnFamily{}},
}},
}
_, err := s.ModifyColumnFamilies(ctx, req)
@ -57,8 +57,8 @@ func TestConcurrentMutationsReadModifyAndGC(t *testing.T) {
Name: name,
Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{
Id: "cf",
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Update{&btapb.ColumnFamily{
GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{1}},
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Update{Update: &btapb.ColumnFamily{
GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}},
}},
}},
}
@ -70,7 +70,7 @@ func TestConcurrentMutationsReadModifyAndGC(t *testing.T) {
var ts int64
ms := func() []*btpb.Mutation {
return []*btpb.Mutation{{
Mutation: &btpb.Mutation_SetCell_{&btpb.Mutation_SetCell{
Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{
FamilyName: "cf",
ColumnQualifier: []byte(`col`),
TimestampMicros: atomic.AddInt64(&ts, 1000),
@ -85,7 +85,7 @@ func TestConcurrentMutationsReadModifyAndGC(t *testing.T) {
Rules: []*btpb.ReadModifyWriteRule{{
FamilyName: "cf",
ColumnQualifier: []byte("col"),
Rule: &btpb.ReadModifyWriteRule_IncrementAmount{1},
Rule: &btpb.ReadModifyWriteRule_IncrementAmount{IncrementAmount: 1},
}},
}
}
@ -139,8 +139,8 @@ func TestCreateTableWithFamily(t *testing.T) {
ctx := context.Background()
newTbl := btapb.Table{
ColumnFamilies: map[string]*btapb.ColumnFamily{
"cf1": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{123}}},
"cf2": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{456}}},
"cf1": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 123}}},
"cf2": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 456}}},
},
}
cTbl, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl})
@ -184,7 +184,7 @@ func TestSampleRowKeys(t *testing.T) {
ctx := context.Background()
newTbl := btapb.Table{
ColumnFamilies: map[string]*btapb.ColumnFamily{
"cf": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{1}}},
"cf": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}},
},
}
tbl, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl})
@ -200,7 +200,7 @@ func TestSampleRowKeys(t *testing.T) {
TableName: tbl.Name,
RowKey: []byte("row-" + strconv.Itoa(i)),
Mutations: []*btpb.Mutation{{
Mutation: &btpb.Mutation_SetCell_{&btpb.Mutation_SetCell{
Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{
FamilyName: "cf",
ColumnQualifier: []byte("col"),
TimestampMicros: 0,
@ -235,7 +235,7 @@ func TestDropRowRange(t *testing.T) {
ctx := context.Background()
newTbl := btapb.Table{
ColumnFamilies: map[string]*btapb.ColumnFamily{
"cf": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{1}}},
"cf": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}},
},
}
tblInfo, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl})
@ -255,7 +255,7 @@ func TestDropRowRange(t *testing.T) {
TableName: tblInfo.Name,
RowKey: []byte(prefix + strconv.Itoa(i)),
Mutations: []*btpb.Mutation{{
Mutation: &btpb.Mutation_SetCell_{&btpb.Mutation_SetCell{
Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{
FamilyName: "cf",
ColumnQualifier: []byte("col"),
TimestampMicros: 0,
@ -274,7 +274,7 @@ func TestDropRowRange(t *testing.T) {
tblSize := tbl.rows.Len()
req := &btapb.DropRowRangeRequest{
Name: tblInfo.Name,
Target: &btapb.DropRowRangeRequest_RowKeyPrefix{[]byte("AAA")},
Target: &btapb.DropRowRangeRequest_RowKeyPrefix{RowKeyPrefix: []byte("AAA")},
}
if _, err = s.DropRowRange(ctx, req); err != nil {
t.Fatalf("Dropping first range: %v", err)
@ -286,7 +286,7 @@ func TestDropRowRange(t *testing.T) {
req = &btapb.DropRowRangeRequest{
Name: tblInfo.Name,
Target: &btapb.DropRowRangeRequest_RowKeyPrefix{[]byte("DDD")},
Target: &btapb.DropRowRangeRequest_RowKeyPrefix{RowKeyPrefix: []byte("DDD")},
}
if _, err = s.DropRowRange(ctx, req); err != nil {
t.Fatalf("Dropping second range: %v", err)
@ -298,7 +298,7 @@ func TestDropRowRange(t *testing.T) {
req = &btapb.DropRowRangeRequest{
Name: tblInfo.Name,
Target: &btapb.DropRowRangeRequest_RowKeyPrefix{[]byte("XXX")},
Target: &btapb.DropRowRangeRequest_RowKeyPrefix{RowKeyPrefix: []byte("XXX")},
}
if _, err = s.DropRowRange(ctx, req); err != nil {
t.Fatalf("Dropping invalid range: %v", err)
@ -310,7 +310,7 @@ func TestDropRowRange(t *testing.T) {
req = &btapb.DropRowRangeRequest{
Name: tblInfo.Name,
Target: &btapb.DropRowRangeRequest_DeleteAllDataFromTable{true},
Target: &btapb.DropRowRangeRequest_DeleteAllDataFromTable{DeleteAllDataFromTable: true},
}
if _, err = s.DropRowRange(ctx, req); err != nil {
t.Fatalf("Dropping all data: %v", err)
@ -326,7 +326,7 @@ func TestDropRowRange(t *testing.T) {
req = &btapb.DropRowRangeRequest{
Name: tblInfo.Name,
Target: &btapb.DropRowRangeRequest_DeleteAllDataFromTable{true},
Target: &btapb.DropRowRangeRequest_DeleteAllDataFromTable{DeleteAllDataFromTable: true},
}
if _, err = s.DropRowRange(ctx, req); err != nil {
t.Fatalf("Dropping all data: %v", err)
@ -344,7 +344,7 @@ func TestDropRowRange(t *testing.T) {
req = &btapb.DropRowRangeRequest{
Name: tblInfo.Name,
Target: &btapb.DropRowRangeRequest_RowKeyPrefix{[]byte("BBB")},
Target: &btapb.DropRowRangeRequest_RowKeyPrefix{RowKeyPrefix: []byte("BBB")},
}
if _, err = s.DropRowRange(ctx, req); err != nil {
t.Fatalf("Dropping range: %v", err)
@ -373,7 +373,7 @@ func TestReadRows(t *testing.T) {
}
newTbl := btapb.Table{
ColumnFamilies: map[string]*btapb.ColumnFamily{
"cf0": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{1}}},
"cf0": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}},
},
}
tblInfo, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl})
@ -384,7 +384,7 @@ func TestReadRows(t *testing.T) {
TableName: tblInfo.Name,
RowKey: []byte("row"),
Mutations: []*btpb.Mutation{{
Mutation: &btpb.Mutation_SetCell_{&btpb.Mutation_SetCell{
Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{
FamilyName: "cf0",
ColumnQualifier: []byte("col"),
TimestampMicros: 1000,
@ -398,11 +398,11 @@ func TestReadRows(t *testing.T) {
for _, rowset := range []*btpb.RowSet{
{RowKeys: [][]byte{[]byte("row")}},
{RowRanges: []*btpb.RowRange{{StartKey: &btpb.RowRange_StartKeyClosed{[]byte("")}}}},
{RowRanges: []*btpb.RowRange{{StartKey: &btpb.RowRange_StartKeyClosed{[]byte("r")}}}},
{RowRanges: []*btpb.RowRange{{StartKey: &btpb.RowRange_StartKeyClosed{StartKeyClosed: []byte("")}}}},
{RowRanges: []*btpb.RowRange{{StartKey: &btpb.RowRange_StartKeyClosed{StartKeyClosed: []byte("r")}}}},
{RowRanges: []*btpb.RowRange{{
StartKey: &btpb.RowRange_StartKeyClosed{[]byte("")},
EndKey: &btpb.RowRange_EndKeyOpen{[]byte("s")},
StartKey: &btpb.RowRange_StartKeyClosed{StartKeyClosed: []byte("")},
EndKey: &btpb.RowRange_EndKeyOpen{EndKeyOpen: []byte("s")},
}}},
} {
mock := &MockReadRowsServer{}
@ -423,7 +423,7 @@ func TestReadRowsOrder(t *testing.T) {
ctx := context.Background()
newTbl := btapb.Table{
ColumnFamilies: map[string]*btapb.ColumnFamily{
"cf0": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{1}}},
"cf0": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}},
},
}
tblInfo, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl})
@ -436,7 +436,7 @@ func TestReadRowsOrder(t *testing.T) {
Name: tblInfo.Name,
Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{
Id: "cf" + strconv.Itoa(i),
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{&btapb.ColumnFamily{}},
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{Create: &btapb.ColumnFamily{}},
}},
}
}
@ -454,7 +454,7 @@ func TestReadRowsOrder(t *testing.T) {
TableName: tblInfo.Name,
RowKey: []byte("row"),
Mutations: []*btpb.Mutation{{
Mutation: &btpb.Mutation_SetCell_{&btpb.Mutation_SetCell{
Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{
FamilyName: "cf" + strconv.Itoa(fc),
ColumnQualifier: []byte("col" + strconv.Itoa(cc)),
TimestampMicros: int64((tc + 1) * 1000),
@ -512,16 +512,17 @@ func TestReadRowsOrder(t *testing.T) {
// Read with interleave filter
inter := &btpb.RowFilter_Interleave{}
fnr := &btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{"1"}}
cqr := &btpb.RowFilter{Filter: &btpb.RowFilter_ColumnQualifierRegexFilter{[]byte("2")}}
fnr := &btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{FamilyNameRegexFilter: "1"}}
cqr := &btpb.RowFilter{Filter: &btpb.RowFilter_ColumnQualifierRegexFilter{ColumnQualifierRegexFilter: []byte("2")}}
inter.Filters = append(inter.Filters, fnr, cqr)
req = &btpb.ReadRowsRequest{
TableName: tblInfo.Name,
Rows: &btpb.RowSet{RowKeys: [][]byte{[]byte("row")}},
Filter: &btpb.RowFilter{
Filter: &btpb.RowFilter_Interleave_{inter},
Filter: &btpb.RowFilter_Interleave_{Interleave: inter},
},
}
mock = &MockReadRowsServer{}
if err = s.ReadRows(req, mock); err != nil {
t.Errorf("ReadRows error: %v", err)
@ -542,7 +543,7 @@ func TestReadRowsOrder(t *testing.T) {
Rules: []*btpb.ReadModifyWriteRule{{
FamilyName: "cf3",
ColumnQualifier: []byte("col" + strconv.Itoa(i)),
Rule: &btpb.ReadModifyWriteRule_IncrementAmount{1},
Rule: &btpb.ReadModifyWriteRule_IncrementAmount{IncrementAmount: 1},
}},
}
}
@ -573,7 +574,7 @@ func TestCheckAndMutateRowWithoutPredicate(t *testing.T) {
ctx := context.Background()
newTbl := btapb.Table{
ColumnFamilies: map[string]*btapb.ColumnFamily{
"cf": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{1}}},
"cf": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}},
},
}
tbl, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl})
@ -587,7 +588,7 @@ func TestCheckAndMutateRowWithoutPredicate(t *testing.T) {
TableName: tbl.Name,
RowKey: []byte("row-present"),
Mutations: []*btpb.Mutation{{
Mutation: &btpb.Mutation_SetCell_{&btpb.Mutation_SetCell{
Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{
FamilyName: "cf",
ColumnQualifier: []byte("col"),
TimestampMicros: 0,
@ -619,3 +620,99 @@ func TestCheckAndMutateRowWithoutPredicate(t *testing.T) {
t.Errorf("Invalid PredicateMatched value: got %t, want %t", got, want)
}
}
// helper function to populate table data
func populateTable(ctx context.Context, s *server) (*btapb.Table, error) {
newTbl := btapb.Table{
ColumnFamilies: map[string]*btapb.ColumnFamily{
"cf0": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{1}}},
},
}
tblInfo, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl})
if err != nil {
return nil, err
}
count := 3
mcf := func(i int) *btapb.ModifyColumnFamiliesRequest {
return &btapb.ModifyColumnFamiliesRequest{
Name: tblInfo.Name,
Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{
Id: "cf" + strconv.Itoa(i),
Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{&btapb.ColumnFamily{}},
}},
}
}
for i := 1; i <= count; i++ {
_, err = s.ModifyColumnFamilies(ctx, mcf(i))
if err != nil {
return nil, err
}
}
// Populate the table
for fc := 0; fc < count; fc++ {
for cc := count; cc > 0; cc-- {
for tc := 0; tc < count; tc++ {
req := &btpb.MutateRowRequest{
TableName: tblInfo.Name,
RowKey: []byte("row"),
Mutations: []*btpb.Mutation{{
Mutation: &btpb.Mutation_SetCell_{&btpb.Mutation_SetCell{
FamilyName: "cf" + strconv.Itoa(fc),
ColumnQualifier: []byte("col" + strconv.Itoa(cc)),
TimestampMicros: int64((tc + 1) * 1000),
Value: []byte{},
}},
}},
}
if _, err := s.MutateRow(ctx, req); err != nil {
return nil, err
}
}
}
}
return tblInfo, nil
}
func TestFilters(t *testing.T) {
tests := []struct {
in *btpb.RowFilter
out int
}{
{in: &btpb.RowFilter{Filter: &btpb.RowFilter_BlockAllFilter{true}}, out: 0},
{in: &btpb.RowFilter{Filter: &btpb.RowFilter_BlockAllFilter{false}}, out: 1},
{in: &btpb.RowFilter{Filter: &btpb.RowFilter_PassAllFilter{true}}, out: 1},
{in: &btpb.RowFilter{Filter: &btpb.RowFilter_PassAllFilter{false}}, out: 0},
}
ctx := context.Background()
s := &server{
tables: make(map[string]*table),
}
tblInfo, err := populateTable(ctx, s)
if err != nil {
t.Fatal(err)
}
req := &btpb.ReadRowsRequest{
TableName: tblInfo.Name,
Rows: &btpb.RowSet{RowKeys: [][]byte{[]byte("row")}},
}
for _, tc := range tests {
req.Filter = tc.in
mock := &MockReadRowsServer{}
if err = s.ReadRows(req, mock); err != nil {
t.Errorf("ReadRows error: %v", err)
continue
}
if len(mock.responses) != tc.out {
t.Errorf("Response count: got %d, want %d", len(mock.responses), tc.out)
continue
}
}
}

View File

@ -1074,12 +1074,12 @@ func doSet(ctx context.Context, args ...string) {
func doSetGCPolicy(ctx context.Context, args ...string) {
if len(args) < 3 {
log.Fatalf("usage: cbt setgcpolicy <table> <family> ( maxage=<d> | maxversions=<n> )")
log.Fatalf("usage: cbt setgcpolicy <table> <family> ( maxage=<d> | maxversions=<n> | maxage=<d> (and|or) maxversions=<n> )")
}
table := args[0]
fam := args[1]
pol, err := parseGCPolicy(args[2])
pol, err := parseGCPolicy(strings.Join(args[2:], " "))
if err != nil {
log.Fatal(err)
}
@ -1101,24 +1101,55 @@ func doWaitForReplicaiton(ctx context.Context, args ...string) {
}
func parseGCPolicy(policyStr string) (bigtable.GCPolicy, error) {
var pol bigtable.GCPolicy
switch p := policyStr; {
case strings.HasPrefix(p, "maxage="):
d, err := parseDuration(p[7:])
words := strings.Fields(policyStr)
switch len(words) {
case 1:
return parseSinglePolicy(words[0])
case 3:
p1, err := parseSinglePolicy(words[0])
if err != nil {
return nil, err
}
pol = bigtable.MaxAgePolicy(d)
case strings.HasPrefix(p, "maxversions="):
n, err := strconv.ParseUint(p[12:], 10, 16)
p2, err := parseSinglePolicy(words[2])
if err != nil {
return nil, err
}
pol = bigtable.MaxVersionsPolicy(int(n))
switch words[1] {
case "and":
return bigtable.IntersectionPolicy(p1, p2), nil
case "or":
return bigtable.UnionPolicy(p1, p2), nil
default:
return nil, fmt.Errorf("Expected 'and' or 'or', saw %q", words[1])
}
default:
return nil, fmt.Errorf("Bad GC policy %q", p)
return nil, fmt.Errorf("Expected '1' or '3' parameter count, saw %d", len(words))
}
return pol, nil
return nil, nil
}
func parseSinglePolicy(s string) (bigtable.GCPolicy, error) {
words := strings.Split(s, "=")
if len(words) != 2 {
return nil, fmt.Errorf("Expected 'name=value', got %q", words)
}
switch words[0] {
case "maxage":
d, err := parseDuration(words[1])
if err != nil {
return nil, err
}
return bigtable.MaxAgePolicy(d), nil
case "maxversions":
n, err := strconv.ParseUint(words[1], 10, 16)
if err != nil {
return nil, err
}
return bigtable.MaxVersionsPolicy(int(n)), nil
default:
return nil, fmt.Errorf("Expected 'maxage' or 'maxversions', got %q", words[1])
}
return nil, nil
}
func parseStorageType(storageTypeStr string) (bigtable.StorageType, error) {

View File

@ -17,6 +17,9 @@ package main
import (
"testing"
"time"
"cloud.google.com/go/bigtable"
"github.com/google/go-cmp/cmp"
)
func TestParseDuration(t *testing.T) {
@ -57,3 +60,54 @@ func TestParseDuration(t *testing.T) {
}
}
}
func TestParseGCPolicy(t *testing.T) {
tests := []struct {
in string
out bigtable.GCPolicy
fail bool
}{
{in: "maxage=1h", out: bigtable.MaxAgePolicy(time.Hour * 1)},
{in: "maxversions=2", out: bigtable.MaxVersionsPolicy(int(2))},
{in: "maxversions=2 and maxage=1h", out: bigtable.IntersectionPolicy([]bigtable.GCPolicy{bigtable.MaxVersionsPolicy(int(2)), bigtable.MaxAgePolicy(time.Hour * 1)}...)},
{in: "maxversions=2 or maxage=1h", out: bigtable.UnionPolicy([]bigtable.GCPolicy{bigtable.MaxVersionsPolicy(int(2)), bigtable.MaxAgePolicy(time.Hour * 1)}...)},
{in: "maxage=1", fail: true},
{in: "maxage = 1h", fail: true},
{in: "maxage =1h", fail: true},
{in: "maxage= 1h", fail: true},
{in: "foomaxage=1h", fail: true},
{in: "maxversions=1h", fail: true},
{in: "maxversions= 1", fail: true},
{in: "maxversions = 1", fail: true},
{in: "maxversions =1", fail: true},
{in: "barmaxversions=1", fail: true},
{in: "maxage = 1h or maxversions=1h", fail: true},
{in: "foomaxversions=2 or maxage=1h", fail: true},
{in: "maxversions=2 or barmaxage=1h", fail: true},
{in: "foomaxversions=2 or barmaxage=1h", fail: true},
{in: "maxage = 1h and maxversions=1h", fail: true},
{in: "foomaxage=1h and maxversions=1", fail: true},
{in: "maxage=1h and barmaxversions=1", fail: true},
{in: "foomaxage=1h and barmaxversions=1", fail: true},
}
for _, tc := range tests {
got, err := parseGCPolicy(tc.in)
if !tc.fail && err != nil {
t.Errorf("parseGCPolicy(%q) unexpectedly failed: %v", tc.in, err)
continue
}
if tc.fail && err == nil {
t.Errorf("parseGCPolicy(%q) did not fail", tc.in)
continue
}
if tc.fail {
continue
}
var cmpOpts cmp.Options
cmpOpts = append(cmpOpts, cmp.AllowUnexported(bigtable.IntersectionPolicy([]bigtable.GCPolicy{}...)), cmp.AllowUnexported(bigtable.UnionPolicy([]bigtable.GCPolicy{}...)))
if !cmp.Equal(got, tc.out, cmpOpts) {
t.Errorf("parseGCPolicy(%q) =%v, want %v", tc.in, got, tc.out)
}
}
}

View File

@ -51,7 +51,7 @@ func (cf chainFilter) proto() *btpb.RowFilter {
chain.Filters = append(chain.Filters, sf.proto())
}
return &btpb.RowFilter{
Filter: &btpb.RowFilter_Chain_{chain},
Filter: &btpb.RowFilter_Chain_{Chain: chain},
}
}
@ -77,7 +77,7 @@ func (ilf interleaveFilter) proto() *btpb.RowFilter {
inter.Filters = append(inter.Filters, sf.proto())
}
return &btpb.RowFilter{
Filter: &btpb.RowFilter_Interleave_{inter},
Filter: &btpb.RowFilter_Interleave_{Interleave: inter},
}
}
@ -91,7 +91,7 @@ type rowKeyFilter string
func (rkf rowKeyFilter) String() string { return fmt.Sprintf("row(%s)", string(rkf)) }
func (rkf rowKeyFilter) proto() *btpb.RowFilter {
return &btpb.RowFilter{Filter: &btpb.RowFilter_RowKeyRegexFilter{[]byte(rkf)}}
return &btpb.RowFilter{Filter: &btpb.RowFilter_RowKeyRegexFilter{RowKeyRegexFilter: []byte(rkf)}}
}
// FamilyFilter returns a filter that matches cells whose family name
@ -104,7 +104,7 @@ type familyFilter string
func (ff familyFilter) String() string { return fmt.Sprintf("col(%s:)", string(ff)) }
func (ff familyFilter) proto() *btpb.RowFilter {
return &btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{string(ff)}}
return &btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{FamilyNameRegexFilter: string(ff)}}
}
// ColumnFilter returns a filter that matches cells whose column name
@ -117,7 +117,7 @@ type columnFilter string
func (cf columnFilter) String() string { return fmt.Sprintf("col(.*:%s)", string(cf)) }
func (cf columnFilter) proto() *btpb.RowFilter {
return &btpb.RowFilter{Filter: &btpb.RowFilter_ColumnQualifierRegexFilter{[]byte(cf)}}
return &btpb.RowFilter{Filter: &btpb.RowFilter_ColumnQualifierRegexFilter{ColumnQualifierRegexFilter: []byte(cf)}}
}
// ValueFilter returns a filter that matches cells whose value
@ -130,7 +130,7 @@ type valueFilter string
func (vf valueFilter) String() string { return fmt.Sprintf("value_match(%s)", string(vf)) }
func (vf valueFilter) proto() *btpb.RowFilter {
return &btpb.RowFilter{Filter: &btpb.RowFilter_ValueRegexFilter{[]byte(vf)}}
return &btpb.RowFilter{Filter: &btpb.RowFilter_ValueRegexFilter{ValueRegexFilter: []byte(vf)}}
}
// LatestNFilter returns a filter that matches the most recent N cells in each column.
@ -141,7 +141,7 @@ type latestNFilter int32
func (lnf latestNFilter) String() string { return fmt.Sprintf("col(*,%d)", lnf) }
func (lnf latestNFilter) proto() *btpb.RowFilter {
return &btpb.RowFilter{Filter: &btpb.RowFilter_CellsPerColumnLimitFilter{int32(lnf)}}
return &btpb.RowFilter{Filter: &btpb.RowFilter_CellsPerColumnLimitFilter{CellsPerColumnLimitFilter: int32(lnf)}}
}
// StripValueFilter returns a filter that replaces each value with the empty string.
@ -151,7 +151,7 @@ type stripValueFilter struct{}
func (stripValueFilter) String() string { return "strip_value()" }
func (stripValueFilter) proto() *btpb.RowFilter {
return &btpb.RowFilter{Filter: &btpb.RowFilter_StripValueTransformer{true}}
return &btpb.RowFilter{Filter: &btpb.RowFilter_StripValueTransformer{StripValueTransformer: true}}
}
// TimestampRangeFilter returns a filter that matches any cells whose timestamp is within the given time bounds. A zero
@ -186,11 +186,10 @@ func (trf timestampRangeFilter) String() string {
func (trf timestampRangeFilter) proto() *btpb.RowFilter {
return &btpb.RowFilter{
Filter: &btpb.RowFilter_TimestampRangeFilter{
&btpb.TimestampRange{
int64(trf.startTime.TruncateToMilliseconds()),
int64(trf.endTime.TruncateToMilliseconds()),
},
Filter: &btpb.RowFilter_TimestampRangeFilter{TimestampRangeFilter: &btpb.TimestampRange{
StartTimestampMicros: int64(trf.startTime.TruncateToMilliseconds()),
EndTimestampMicros: int64(trf.endTime.TruncateToMilliseconds()),
},
}}
}
@ -213,12 +212,12 @@ func (crf columnRangeFilter) String() string {
func (crf columnRangeFilter) proto() *btpb.RowFilter {
r := &btpb.ColumnRange{FamilyName: crf.family}
if crf.start != "" {
r.StartQualifier = &btpb.ColumnRange_StartQualifierClosed{[]byte(crf.start)}
r.StartQualifier = &btpb.ColumnRange_StartQualifierClosed{StartQualifierClosed: []byte(crf.start)}
}
if crf.end != "" {
r.EndQualifier = &btpb.ColumnRange_EndQualifierOpen{[]byte(crf.end)}
r.EndQualifier = &btpb.ColumnRange_EndQualifierOpen{EndQualifierOpen: []byte(crf.end)}
}
return &btpb.RowFilter{&btpb.RowFilter_ColumnRangeFilter{r}}
return &btpb.RowFilter{Filter: &btpb.RowFilter_ColumnRangeFilter{ColumnRangeFilter: r}}
}
// ValueRangeFilter returns a filter that matches cells with values that fall within
@ -239,12 +238,12 @@ func (vrf valueRangeFilter) String() string {
func (vrf valueRangeFilter) proto() *btpb.RowFilter {
r := &btpb.ValueRange{}
if vrf.start != nil {
r.StartValue = &btpb.ValueRange_StartValueClosed{vrf.start}
r.StartValue = &btpb.ValueRange_StartValueClosed{StartValueClosed: vrf.start}
}
if vrf.end != nil {
r.EndValue = &btpb.ValueRange_EndValueOpen{vrf.end}
r.EndValue = &btpb.ValueRange_EndValueOpen{EndValueOpen: vrf.end}
}
return &btpb.RowFilter{&btpb.RowFilter_ValueRangeFilter{r}}
return &btpb.RowFilter{Filter: &btpb.RowFilter_ValueRangeFilter{ValueRangeFilter: r}}
}
// ConditionFilter returns a filter that evaluates to one of two possible filters depending
@ -278,10 +277,10 @@ func (cf conditionFilter) proto() *btpb.RowFilter {
ff = cf.falseFilter.proto()
}
return &btpb.RowFilter{
&btpb.RowFilter_Condition_{&btpb.RowFilter_Condition{
cf.predicateFilter.proto(),
tf,
ff,
Filter: &btpb.RowFilter_Condition_{Condition: &btpb.RowFilter_Condition{
PredicateFilter: cf.predicateFilter.proto(),
TrueFilter: tf,
FalseFilter: ff,
}}}
}
@ -297,7 +296,7 @@ func (cof cellsPerRowOffsetFilter) String() string {
}
func (cof cellsPerRowOffsetFilter) proto() *btpb.RowFilter {
return &btpb.RowFilter{Filter: &btpb.RowFilter_CellsPerRowOffsetFilter{int32(cof)}}
return &btpb.RowFilter{Filter: &btpb.RowFilter_CellsPerRowOffsetFilter{CellsPerRowOffsetFilter: int32(cof)}}
}
// CellsPerRowLimitFilter returns a filter that matches only the first N cells of each row.
@ -312,7 +311,7 @@ func (clf cellsPerRowLimitFilter) String() string {
}
func (clf cellsPerRowLimitFilter) proto() *btpb.RowFilter {
return &btpb.RowFilter{Filter: &btpb.RowFilter_CellsPerRowLimitFilter{int32(clf)}}
return &btpb.RowFilter{Filter: &btpb.RowFilter_CellsPerRowLimitFilter{CellsPerRowLimitFilter: int32(clf)}}
}
// TODO(dsymonds): More filters: sampling

View File

@ -52,7 +52,7 @@ func (ip intersectionPolicy) proto() *bttdpb.GcRule {
inter.Rules = append(inter.Rules, sp.proto())
}
return &bttdpb.GcRule{
Rule: &bttdpb.GcRule_Intersection_{inter},
Rule: &bttdpb.GcRule_Intersection_{Intersection: inter},
}
}
@ -77,7 +77,7 @@ func (up unionPolicy) proto() *bttdpb.GcRule {
union.Rules = append(union.Rules, sp.proto())
}
return &bttdpb.GcRule{
Rule: &bttdpb.GcRule_Union_{union},
Rule: &bttdpb.GcRule_Union_{Union: union},
}
}
@ -90,7 +90,7 @@ type maxVersionsPolicy int
func (mvp maxVersionsPolicy) String() string { return fmt.Sprintf("versions() > %d", int(mvp)) }
func (mvp maxVersionsPolicy) proto() *bttdpb.GcRule {
return &bttdpb.GcRule{Rule: &bttdpb.GcRule_MaxNumVersions{int32(mvp)}}
return &bttdpb.GcRule{Rule: &bttdpb.GcRule_MaxNumVersions{MaxNumVersions: int32(mvp)}}
}
// MaxAgePolicy returns a GC policy that applies to all cells
@ -123,7 +123,7 @@ func (ma maxAgePolicy) proto() *bttdpb.GcRule {
// Fix this if people care about GC policies over 290 years.
ns := time.Duration(ma).Nanoseconds()
return &bttdpb.GcRule{
Rule: &bttdpb.GcRule_MaxAge{&durpb.Duration{
Rule: &bttdpb.GcRule_MaxAge{MaxAge: &durpb.Duration{
Seconds: ns / 1e9,
Nanos: int32(ns % 1e9),
}},

68
vendor/cloud.google.com/go/bigtable/go18.go generated vendored Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2018 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.
// +build go1.8
package bigtable
import (
"fmt"
"go.opencensus.io/plugin/ocgrpc"
"go.opencensus.io/trace"
"golang.org/x/net/context"
"google.golang.org/api/option"
"google.golang.org/grpc"
)
func openCensusOptions() []option.ClientOption {
return []option.ClientOption{
option.WithGRPCDialOption(grpc.WithStatsHandler(&ocgrpc.ClientHandler{})),
}
}
func traceStartSpan(ctx context.Context, name string) context.Context {
ctx, _ = trace.StartSpan(ctx, name)
return ctx
}
func traceEndSpan(ctx context.Context, err error) {
span := trace.FromContext(ctx)
if err != nil {
span.SetStatus(trace.Status{Message: err.Error()})
}
span.End()
}
func tracePrintf(ctx context.Context, attrMap map[string]interface{}, format string, args ...interface{}) {
var attrs []trace.Attribute
for k, v := range attrMap {
var a trace.Attribute
switch v := v.(type) {
case string:
a = trace.StringAttribute(k, v)
case bool:
a = trace.BoolAttribute(k, v)
case int:
a = trace.Int64Attribute(k, int64(v))
case int64:
a = trace.Int64Attribute(k, v)
default:
a = trace.StringAttribute(k, fmt.Sprintf("%#v", v))
}
attrs = append(attrs, a)
}
trace.FromContext(ctx).Annotatef(attrs, format, args...)
}

View File

@ -20,8 +20,8 @@ import (
"time"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestRandomizedDelays(t *testing.T) {
@ -43,7 +43,7 @@ func TestRandomizedDelays(t *testing.T) {
}
invokeTime = time.Now()
// Workaround for `go vet`: https://github.com/grpc/grpc-go/issues/90
errf := grpc.Errorf
errf := status.Errorf
return errf(codes.Unavailable, "")
}, settings...)
}

36
vendor/cloud.google.com/go/bigtable/not_go18.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2017 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.
// +build !go1.8
package bigtable
import (
"golang.org/x/net/context"
"google.golang.org/api/option"
)
// OpenCensus only supports go 1.8 and higher.
func openCensusOptions() []option.ClientOption { return nil }
func traceStartSpan(ctx context.Context, _ string) context.Context {
return ctx
}
func traceEndSpan(context.Context, error) {
}
func tracePrintf(context.Context, map[string]interface{}, string, ...interface{}) {
}

View File

@ -30,6 +30,7 @@ import (
rpcpb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func setupFakeServer(opt ...grpc.ServerOption) (tbl *Table, cleanup func(), err error) {
@ -42,12 +43,12 @@ func setupFakeServer(opt ...grpc.ServerOption) (tbl *Table, cleanup func(), err
return nil, nil, err
}
client, err := NewClient(context.Background(), "client", "instance", option.WithGRPCConn(conn))
client, err := NewClient(context.Background(), "client", "instance", option.WithGRPCConn(conn), option.WithGRPCDialOption(grpc.WithBlock()))
if err != nil {
return nil, nil, err
}
adminClient, err := NewAdminClient(context.Background(), "client", "instance", option.WithGRPCConn(conn))
adminClient, err := NewAdminClient(context.Background(), "client", "instance", option.WithGRPCConn(conn), option.WithGRPCDialOption(grpc.WithBlock()))
if err != nil {
return nil, nil, err
}
@ -76,7 +77,7 @@ func TestRetryApply(t *testing.T) {
errInjector := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
if strings.HasSuffix(info.FullMethod, "MutateRow") && errCount < 3 {
errCount++
return nil, grpc.Errorf(code, "")
return nil, status.Errorf(code, "")
}
return handler(ctx, req)
}
@ -156,7 +157,7 @@ func TestRetryApplyBulk(t *testing.T) {
f = func(ss grpc.ServerStream) error {
if errCount < 3 {
errCount++
return grpc.Errorf(codes.Aborted, "")
return status.Errorf(codes.Aborted, "")
}
return nil
}
@ -182,7 +183,7 @@ func TestRetryApplyBulk(t *testing.T) {
switch errCount {
case 0:
// Retryable request failure
err = grpc.Errorf(codes.Unavailable, "")
err = status.Errorf(codes.Unavailable, "")
case 1:
// Two mutations fail
writeMutateRowsResponse(ss, codes.Unavailable, codes.OK, codes.Aborted)
@ -235,8 +236,8 @@ func TestRetryApplyBulk(t *testing.T) {
t.Errorf("unretryable errors: request failed %v", err)
}
want := []error{
grpc.Errorf(codes.FailedPrecondition, ""),
grpc.Errorf(codes.Aborted, ""),
status.Errorf(codes.FailedPrecondition, ""),
status.Errorf(codes.Aborted, ""),
}
if !testutil.Equal(want, errors) {
t.Errorf("unretryable errors: got: %v, want: %v", errors, want)
@ -323,20 +324,20 @@ func TestRetryReadRows(t *testing.T) {
switch errCount {
case 0:
// Retryable request failure
err = grpc.Errorf(codes.Unavailable, "")
err = status.Errorf(codes.Unavailable, "")
case 1:
// Write two rows then error
if want, got := "a", string(req.Rows.RowRanges[0].GetStartKeyClosed()); want != got {
t.Errorf("first retry, no data received yet: got %q, want %q", got, want)
}
writeReadRowsResponse(ss, "a", "b")
err = grpc.Errorf(codes.Unavailable, "")
err = status.Errorf(codes.Unavailable, "")
case 2:
// Retryable request failure
if want, got := "b\x00", string(req.Rows.RowRanges[0].GetStartKeyClosed()); want != got {
t.Errorf("2 range retries: got %q, want %q", got, want)
}
err = grpc.Errorf(codes.Unavailable, "")
err = status.Errorf(codes.Unavailable, "")
case 3:
// Write two more rows
writeReadRowsResponse(ss, "c", "d")

30
vendor/cloud.google.com/go/cloud.go generated vendored
View File

@ -12,9 +12,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package cloud is the root of the packages used to access Google Cloud
// Services. See https://godoc.org/cloud.google.com/go for a full list
// of sub-packages.
//
// This package documents how to authorize and authenticate the sub packages.
/*
Package cloud is the root of the packages used to access Google Cloud
Services. See https://godoc.org/cloud.google.com/go for a full list
of sub-packages.
Examples in this package show ways to authorize and authenticate the
sub packages.
Connection Pooling
Connection pooling differs in clients based on their transport. Cloud
clients either rely on HTTP or gRPC transports to communicate
with Google Cloud.
Cloud clients that use HTTP (bigquery, compute, storage, and translate) rely on the
underlying HTTP transport to cache connections for later re-use. These are cached to
the default http.MaxIdleConns and http.MaxIdleConnsPerHost settings in
http.DefaultTransport.
For gPRC clients (all others in this repo), connection pooling is configurable. Users
of cloud client libraries may specify option.WithGRPCConnectionPool(n) as a client
option to NewClient calls. This configures the underlying gRPC connections to be
pooled and addressed in a round robin fashion.
*/
package cloud // import "cloud.google.com/go"

View File

@ -43,15 +43,15 @@ func TestValueCollector(t *testing.T) {
c := NewCollector(&Program{}, 26)
// Add some variables of various types, whose values we want the collector to read.
variablesToAdd := []debug.LocalVar{
{Name: "a", Var: debug.Var{int16Type, 0x1}},
{Name: "b", Var: debug.Var{stringType, 0x2}},
{Name: "c", Var: debug.Var{structType, 0x3}},
{Name: "d", Var: debug.Var{pointerType, 0x4}},
{Name: "e", Var: debug.Var{arrayType, 0x5}},
{Name: "f", Var: debug.Var{debugStringType, 0x6}},
{Name: "g", Var: debug.Var{mapType, 0x7}},
{Name: "h", Var: debug.Var{channelType, 0x8}},
{Name: "i", Var: debug.Var{sliceType, 0x9}},
{Name: "a", Var: debug.Var{TypeID: int16Type, Address: 0x1}},
{Name: "b", Var: debug.Var{TypeID: stringType, Address: 0x2}},
{Name: "c", Var: debug.Var{TypeID: structType, Address: 0x3}},
{Name: "d", Var: debug.Var{TypeID: pointerType, Address: 0x4}},
{Name: "e", Var: debug.Var{TypeID: arrayType, Address: 0x5}},
{Name: "f", Var: debug.Var{TypeID: debugStringType, Address: 0x6}},
{Name: "g", Var: debug.Var{TypeID: mapType, Address: 0x7}},
{Name: "h", Var: debug.Var{TypeID: channelType, Address: 0x8}},
{Name: "i", Var: debug.Var{TypeID: sliceType, Address: 0x9}},
}
expectedResults := []*cd.Variable{
&cd.Variable{Name: "a", VarTableIndex: 1},
@ -195,17 +195,17 @@ func (p *Program) Value(v debug.Var) (debug.Value, error) {
Fields: []debug.StructField{
{
Name: "x",
Var: debug.Var{int16Type, 0x1},
Var: debug.Var{TypeID: int16Type, Address: 0x1},
},
{
Name: "y",
Var: debug.Var{stringType, 0x2},
Var: debug.Var{TypeID: stringType, Address: 0x2},
},
},
}, nil
case pointerType:
// A pointer to the first variable above.
return debug.Pointer{int16Type, 0x1}, nil
return debug.Pointer{TypeID: int16Type, Address: 0x1}, nil
case arrayType:
// An array of 4 32-bit-wide elements.
return debug.Array{

View File

@ -21,6 +21,7 @@ import (
"os"
"reflect"
"cloud.google.com/go/internal/trace"
"golang.org/x/net/context"
"google.golang.org/api/option"
gtransport "google.golang.org/api/transport/grpc"
@ -302,11 +303,14 @@ func (c *Client) Close() error {
// type than the one it was stored from, or when a field is missing or
// unexported in the destination struct. ErrFieldMismatch is only returned if
// dst is a struct pointer.
func (c *Client) Get(ctx context.Context, key *Key, dst interface{}) error {
func (c *Client) Get(ctx context.Context, key *Key, dst interface{}) (err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Get")
defer func() { trace.EndSpan(ctx, err) }()
if dst == nil { // get catches nil interfaces; we need to catch nil ptr here
return ErrInvalidEntityType
}
err := c.get(ctx, []*Key{key}, []interface{}{dst}, nil)
err = c.get(ctx, []*Key{key}, []interface{}{dst}, nil)
if me, ok := err.(MultiError); ok {
return me[0]
}
@ -323,7 +327,10 @@ func (c *Client) Get(ctx context.Context, key *Key, dst interface{}) error {
// As a special case, PropertyList is an invalid type for dst, even though a
// PropertyList is a slice of structs. It is treated as invalid to avoid being
// mistakenly passed when []PropertyList was intended.
func (c *Client) GetMulti(ctx context.Context, keys []*Key, dst interface{}) error {
func (c *Client) GetMulti(ctx context.Context, keys []*Key, dst interface{}) (err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.GetMulti")
defer func() { trace.EndSpan(ctx, err) }()
return c.get(ctx, keys, dst, nil)
}
@ -452,7 +459,11 @@ func (c *Client) Put(ctx context.Context, key *Key, src interface{}) (*Key, erro
// PutMulti is a batch version of Put.
//
// src must satisfy the same conditions as the dst argument to GetMulti.
func (c *Client) PutMulti(ctx context.Context, keys []*Key, src interface{}) ([]*Key, error) {
// TODO(jba): rewrite in terms of Mutate.
func (c *Client) PutMulti(ctx context.Context, keys []*Key, src interface{}) (_ []*Key, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.PutMulti")
defer func() { trace.EndSpan(ctx, err) }()
mutations, err := putMutations(keys, src)
if err != nil {
return nil, err
@ -540,7 +551,11 @@ func (c *Client) Delete(ctx context.Context, key *Key) error {
}
// DeleteMulti is a batch version of Delete.
func (c *Client) DeleteMulti(ctx context.Context, keys []*Key) error {
// TODO(jba): rewrite in terms of Mutate.
func (c *Client) DeleteMulti(ctx context.Context, keys []*Key) (err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.DeleteMulti")
defer func() { trace.EndSpan(ctx, err) }()
mutations, err := deleteMutations(keys)
if err != nil {
return err
@ -572,3 +587,41 @@ func deleteMutations(keys []*Key) ([]*pb.Mutation, error) {
}
return mutations, nil
}
// Mutate applies one or more mutations atomically.
// It returns the keys of the argument Mutations, in the same order.
//
// If any of the mutations are invalid, Mutate returns a MultiError with the errors.
// Mutate returns a MultiError in this case even if there is only one Mutation.
func (c *Client) Mutate(ctx context.Context, muts ...*Mutation) (_ []*Key, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Mutate")
defer func() { trace.EndSpan(ctx, err) }()
pmuts, err := mutationProtos(muts)
if err != nil {
return nil, err
}
req := &pb.CommitRequest{
ProjectId: c.dataset,
Mutations: pmuts,
Mode: pb.CommitRequest_NON_TRANSACTIONAL,
}
resp, err := c.client.Commit(ctx, req)
if err != nil {
return nil, err
}
// Copy any newly minted keys into the returned keys.
ret := make([]*Key, len(muts))
for i, mut := range muts {
if mut.key.Incomplete() {
// This key is in the mutation results.
ret[i], err = protoToKey(resp.MutationResults[i].Key)
if err != nil {
return nil, errors.New("datastore: internal error: server returned an invalid key")
}
} else {
ret[i] = mut.key
}
}
return ret, nil
}

Binary file not shown.

View File

@ -263,6 +263,43 @@ type Y2 struct {
F []int64
}
type Pointers struct {
Pi *int
Ps *string
Pb *bool
Pf *float64
Pg *GeoPoint
Pt *time.Time
}
type PointersOmitEmpty struct {
Pi *int `datastore:",omitempty"`
Ps *string `datastore:",omitempty"`
Pb *bool `datastore:",omitempty"`
Pf *float64 `datastore:",omitempty"`
Pg *GeoPoint `datastore:",omitempty"`
Pt *time.Time `datastore:",omitempty"`
}
func populatedPointers() *Pointers {
var (
i int
s string
b bool
f float64
g GeoPoint
t time.Time
)
return &Pointers{
Pi: &i,
Ps: &s,
Pb: &b,
Pf: &f,
Pg: &g,
Pt: &t,
}
}
type Tagged struct {
A int `datastore:"a,noindex"`
B []int `datastore:"b"`
@ -406,10 +443,6 @@ type PtrToStructField struct {
var two int = 2
type PtrToInt struct {
I *int
}
type EmbeddedTime struct {
time.Time
}
@ -1645,15 +1678,6 @@ var testCases = []testCase{
"",
"",
},
{
"save struct with pointer to int field",
&PtrToInt{
I: &two,
},
&PtrToInt{},
"unsupported struct field",
"",
},
{
"struct with nil ptr to struct fields",
&PtrToStructField{
@ -1903,6 +1927,20 @@ var testCases = []testCase{
"",
"",
},
{
"pointer fields: nil",
&Pointers{},
&Pointers{},
"",
"",
},
{
"pointer fields: populated with zeroes",
populatedPointers(),
populatedPointers(),
"",
"",
},
}
// checkErr returns the empty string if either both want and err are zero,

View File

@ -15,8 +15,6 @@
/*
Package datastore provides a client for Google Cloud Datastore.
Note: This package is in beta. Some backwards-incompatible changes may occur.
Basic Operations
@ -43,7 +41,8 @@ Valid value types are:
- time.Time (stored with microsecond precision),
- structs whose fields are all valid value types,
- pointers to structs whose fields are all valid value types,
- slices of any of the above.
- slices of any of the above,
- pointers to a signed integer, bool, string, float32, or float64.
Slices of structs are valid, as are structs that contain slices.
@ -86,6 +85,10 @@ GetMulti, PutMulti and DeleteMulti are batch versions of the Get, Put and
Delete functions. They take a []*Key instead of a *Key, and may return a
datastore.MultiError when encountering partial failure.
Mutate generalizes PutMulti and DeleteMulti to a sequence of any Datastore mutations.
It takes a series of mutations created with NewInsert, NewUpdate, NewUpsert and
NewDelete and applies them atomically.
Properties
@ -118,9 +121,10 @@ field name. A "-" tag name means that the datastore will ignore that field.
The only valid options are "omitempty", "noindex" and "flatten".
If the options include "omitempty" and the value of the field is empty, then the field will be omitted on Save.
The empty values are false, 0, any nil interface value, and any array, slice, map, or string of length zero.
Struct field values will never be empty.
If the options include "omitempty" and the value of the field is empty, then the
field will be omitted on Save. The empty values are false, 0, any nil pointer or
interface value, and any array, slice, map, or string of length zero. Struct field
values will never be empty, except for nil pointers.
If options include "noindex" then the field will not be indexed. All fields are indexed
by default. Strings or byte slices longer than 1500 bytes cannot be indexed;
@ -154,6 +158,36 @@ Example code:
}
Slice Fields
A field of slice type corresponds to a Datastore array property, except for []byte, which corresponds
to a Datastore blob.
Zero-length slice fields are not saved. Slice fields of length 1 or greater are saved
as Datastore arrays. When a zero-length Datastore array is loaded into a slice field,
the slice field remains unchanged.
If a non-array value is loaded into a slice field, the result will be a slice with
one element, containing the value.
Loading Nulls
Loading a Datastore Null into a basic type (int, float, etc.) results in a zero value.
Loading a Null into a slice of basic type results in a slice of size 1 containing the zero value.
Loading a Null into a pointer field results in nil.
Loading a Null into a field of struct type is an error.
Pointer Fields
A struct field can be a pointer to a signed integer, floating-point number, string or
bool. Putting a non-nil pointer will store its dereferenced value. Putting a nil
pointer will store a Datastore Null property, unless the field is marked omitempty,
in which case no property will be stored.
Loading a Null into a pointer field sets the pointer to nil. Loading any other value
allocates new storage with the value, and sets the field to point to it.
Key Field
If the struct contains a *datastore.Key field tagged with the name "__key__",
@ -436,6 +470,9 @@ Example code:
fmt.Printf("Count=%d\n", count)
}
Pass the ReadOnly option to RunInTransaction if your transaction is used only for Get,
GetMulti or queries. Read-only transactions are more efficient.
Google Cloud Datastore Emulator
This package supports the Cloud Datastore emulator, which is useful for testing and

View File

@ -396,6 +396,28 @@ func ExampleClient_GetAll() {
}
}
func ExampleClient_Mutate() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
key1 := datastore.NameKey("Post", "post1", nil)
key2 := datastore.NameKey("Post", "post2", nil)
key3 := datastore.NameKey("Post", "post3", nil)
key4 := datastore.NameKey("Post", "post4", nil)
_, err = client.Mutate(ctx,
datastore.NewInsert(key1, Post{Title: "Post 1"}),
datastore.NewUpsert(key2, Post{Title: "Post 2"}),
datastore.NewUpdate(key3, Post{Title: "Post 3"}),
datastore.NewDelete(key4))
if err != nil {
// TODO: Handle error.
}
}
func ExampleCommit_Key() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "")

View File

@ -35,6 +35,8 @@ import (
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// TODO(djd): Make test entity clean up more robust: some test entities may
@ -1051,6 +1053,51 @@ func TestTransaction(t *testing.T) {
}
}
func TestReadOnlyTransaction(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client := newClient(ctx, t, nil)
defer client.Close()
type value struct{ N int }
// Put a value.
const n = 5
v := &value{N: n}
key, err := client.Put(ctx, IncompleteKey("roTxn", nil), v)
if err != nil {
t.Fatal(err)
}
defer client.Delete(ctx, key)
// Read it from a read-only transaction.
_, err = client.RunInTransaction(ctx, func(tx *Transaction) error {
if err := tx.Get(key, v); err != nil {
return err
}
return nil
}, ReadOnly)
if err != nil {
t.Fatal(err)
}
if v.N != n {
t.Fatalf("got %d, want %d", v.N, n)
}
// Attempting to write from a read-only transaction is an error.
_, err = client.RunInTransaction(ctx, func(tx *Transaction) error {
if _, err := tx.Put(key, v); err != nil {
return err
}
return nil
}, ReadOnly)
if err == nil {
t.Fatal("got nil, want error")
}
}
func TestNilPointers(t *testing.T) {
ctx := context.Background()
client := newTestClient(ctx, t)
@ -1115,3 +1162,116 @@ func TestNestedRepeatedElementNoIndex(t *testing.T) {
t.Fatalf("client.Delete: %v", err)
}
}
func TestPointerFields(t *testing.T) {
ctx := context.Background()
client := newTestClient(ctx, t)
defer client.Close()
want := populatedPointers()
key, err := client.Put(ctx, IncompleteKey("pointers", nil), want)
if err != nil {
t.Fatal(err)
}
var got Pointers
if err := client.Get(ctx, key, &got); err != nil {
t.Fatal(err)
}
if got.Pi == nil || *got.Pi != *want.Pi {
t.Errorf("Pi: got %v, want %v", got.Pi, *want.Pi)
}
if got.Ps == nil || *got.Ps != *want.Ps {
t.Errorf("Ps: got %v, want %v", got.Ps, *want.Ps)
}
if got.Pb == nil || *got.Pb != *want.Pb {
t.Errorf("Pb: got %v, want %v", got.Pb, *want.Pb)
}
if got.Pf == nil || *got.Pf != *want.Pf {
t.Errorf("Pf: got %v, want %v", got.Pf, *want.Pf)
}
if got.Pg == nil || *got.Pg != *want.Pg {
t.Errorf("Pg: got %v, want %v", got.Pg, *want.Pg)
}
if got.Pt == nil || !got.Pt.Equal(*want.Pt) {
t.Errorf("Pt: got %v, want %v", got.Pt, *want.Pt)
}
}
func TestMutate(t *testing.T) {
// test Client.Mutate
testMutate(t, func(ctx context.Context, client *Client, muts ...*Mutation) ([]*Key, error) {
return client.Mutate(ctx, muts...)
})
// test Transaction.Mutate
testMutate(t, func(ctx context.Context, client *Client, muts ...*Mutation) ([]*Key, error) {
var pkeys []*PendingKey
commit, err := client.RunInTransaction(ctx, func(tx *Transaction) error {
var err error
pkeys, err = tx.Mutate(muts...)
return err
})
if err != nil {
return nil, err
}
var keys []*Key
for _, pk := range pkeys {
keys = append(keys, commit.Key(pk))
}
return keys, nil
})
}
func testMutate(t *testing.T, mutate func(ctx context.Context, client *Client, muts ...*Mutation) ([]*Key, error)) {
ctx := context.Background()
client := newTestClient(ctx, t)
defer client.Close()
type T struct{ I int }
check := func(k *Key, want interface{}) {
var x T
err := client.Get(ctx, k, &x)
switch want := want.(type) {
case error:
if err != want {
t.Errorf("key %s: got error %v, want %v", k, err, want)
}
case int:
if err != nil {
t.Fatalf("key %s: %v", k, err)
}
if x.I != want {
t.Errorf("key %s: got %d, want %d", k, x.I, want)
}
default:
panic("check: bad arg")
}
}
keys, err := mutate(ctx, client,
NewInsert(IncompleteKey("t", nil), &T{1}),
NewUpsert(IncompleteKey("t", nil), &T{2}),
)
if err != nil {
t.Fatal(err)
}
check(keys[0], 1)
check(keys[1], 2)
_, err = mutate(ctx, client,
NewUpdate(keys[0], &T{3}),
NewDelete(keys[1]),
)
check(keys[0], 3)
check(keys[1], ErrNoSuchEntity)
_, err = mutate(ctx, client, NewInsert(keys[0], &T{4}))
if got, want := status.Code(err), codes.AlreadyExists; got != want {
t.Errorf("Insert existing key: got %s, want %s", got, want)
}
_, err = mutate(ctx, client, NewUpdate(keys[1], &T{4}))
if got, want := status.Code(err), codes.NotFound; got != want {
t.Errorf("Update non-existing key: got %s, want %s", got, want)
}
}

View File

@ -60,6 +60,10 @@ func typeMismatchReason(p Property, v reflect.Value) string {
return fmt.Sprintf("type mismatch: %s versus %v", entityType, v.Type())
}
func overflowReason(x interface{}, v reflect.Value) string {
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
}
type propertyLoader struct {
// m holds the number of times a substruct field like "Foo.Bar.Baz" has
// been seen so far. The map is constructed lazily.
@ -243,7 +247,7 @@ func plsFieldLoad(v reflect.Value, p Property, subfields []string) (ok bool, err
}
// setVal sets 'v' to the value of the Property 'p'.
func setVal(v reflect.Value, p Property) string {
func setVal(v reflect.Value, p Property) (s string) {
pValue := p.Value
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@ -252,7 +256,7 @@ func setVal(v reflect.Value, p Property) string {
return typeMismatchReason(p, v)
}
if v.OverflowInt(x) {
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
return overflowReason(x, v)
}
v.SetInt(x)
case reflect.Bool:
@ -273,12 +277,12 @@ func setVal(v reflect.Value, p Property) string {
return typeMismatchReason(p, v)
}
if v.OverflowFloat(x) {
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
return overflowReason(x, v)
}
v.SetFloat(x)
case reflect.Ptr:
// v must be either a pointer to a Key or Entity.
if v.Type() != typeOfKeyPtr && v.Type().Elem().Kind() != reflect.Struct {
// v must be a pointer to either a Key, an Entity, or one of the supported basic types.
if v.Type() != typeOfKeyPtr && v.Type().Elem().Kind() != reflect.Struct && !isValidPointerType(v.Type().Elem()) {
return typeMismatchReason(p, v)
}
@ -290,21 +294,38 @@ func setVal(v reflect.Value, p Property) string {
return ""
}
switch x := pValue.(type) {
case *Key:
if x, ok := p.Value.(*Key); ok {
if _, ok := v.Interface().(*Key); !ok {
return typeMismatchReason(p, v)
}
v.Set(reflect.ValueOf(x))
return ""
}
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
switch x := pValue.(type) {
case *Entity:
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
err := loadEntity(v.Interface(), x)
if err != nil {
return err.Error()
}
case int64:
if v.Elem().OverflowInt(x) {
return overflowReason(x, v.Elem())
}
v.Elem().SetInt(x)
case float64:
if v.Elem().OverflowFloat(x) {
return overflowReason(x, v.Elem())
}
v.Elem().SetFloat(x)
case bool:
v.Elem().SetBool(x)
case string:
v.Elem().SetString(x)
case GeoPoint, time.Time:
v.Elem().Set(reflect.ValueOf(x))
default:
return typeMismatchReason(p, v)
}

View File

@ -17,6 +17,7 @@ package datastore
import (
"reflect"
"testing"
"time"
"cloud.google.com/go/internal/testutil"
@ -755,3 +756,131 @@ func TestKeyLoader(t *testing.T) {
}
}
}
func TestLoadPointers(t *testing.T) {
for _, test := range []struct {
desc string
in []Property
want Pointers
}{
{
desc: "nil properties load as nil pointers",
in: []Property{
Property{Name: "Pi", Value: nil},
Property{Name: "Ps", Value: nil},
Property{Name: "Pb", Value: nil},
Property{Name: "Pf", Value: nil},
Property{Name: "Pg", Value: nil},
Property{Name: "Pt", Value: nil},
},
want: Pointers{},
},
{
desc: "missing properties load as nil pointers",
in: []Property(nil),
want: Pointers{},
},
{
desc: "non-nil properties load as the appropriate values",
in: []Property{
Property{Name: "Pi", Value: int64(1)},
Property{Name: "Ps", Value: "x"},
Property{Name: "Pb", Value: true},
Property{Name: "Pf", Value: 3.14},
Property{Name: "Pg", Value: GeoPoint{Lat: 1, Lng: 2}},
Property{Name: "Pt", Value: time.Unix(100, 0)},
},
want: func() Pointers {
p := populatedPointers()
*p.Pi = 1
*p.Ps = "x"
*p.Pb = true
*p.Pf = 3.14
*p.Pg = GeoPoint{Lat: 1, Lng: 2}
*p.Pt = time.Unix(100, 0)
return *p
}(),
},
} {
var got Pointers
if err := LoadStruct(&got, test.in); err != nil {
t.Fatalf("%s: %v", test.desc, err)
}
if !testutil.Equal(got, test.want) {
t.Errorf("%s:\ngot %+v\nwant %+v", test.desc, got, test.want)
}
}
}
func TestLoadNonArrayIntoSlice(t *testing.T) {
// Loading a non-array value into a slice field results in a slice of size 1.
var got struct{ S []string }
if err := LoadStruct(&got, []Property{{Name: "S", Value: "x"}}); err != nil {
t.Fatal(err)
}
if want := []string{"x"}; !testutil.Equal(got.S, want) {
t.Errorf("got %#v, want %#v", got.S, want)
}
}
func TestLoadEmptyArrayIntoSlice(t *testing.T) {
// Loading an empty array into a slice field is a no-op.
var got = struct{ S []string }{[]string{"x"}}
if err := LoadStruct(&got, []Property{{Name: "S", Value: []interface{}{}}}); err != nil {
t.Fatal(err)
}
if want := []string{"x"}; !testutil.Equal(got.S, want) {
t.Errorf("got %#v, want %#v", got.S, want)
}
}
func TestLoadNull(t *testing.T) {
// Loading a Datastore Null into a basic type (int, float, etc.) results in a zero value.
// Loading a Null into a slice of basic type results in a slice of size 1 containing the zero value.
// (As expected from the behavior of slices and nulls with basic types.)
type S struct {
I int64
F float64
S string
B bool
A []string
}
got := S{
I: 1,
F: 1.0,
S: "1",
B: true,
A: []string{"X"},
}
want := S{A: []string{""}}
props := []Property{{Name: "I"}, {Name: "F"}, {Name: "S"}, {Name: "B"}, {Name: "A"}}
if err := LoadStruct(&got, props); err != nil {
t.Fatal(err)
}
if !testutil.Equal(got, want) {
t.Errorf("got %+v, want %+v", got, want)
}
// Loading a Null into a pointer to struct field results in a nil field.
got2 := struct{ X *S }{X: &S{}}
if err := LoadStruct(&got2, []Property{{Name: "X"}}); err != nil {
t.Fatal(err)
}
if got2.X != nil {
t.Errorf("got %v, want nil", got2.X)
}
// Loading a Null into a struct field is an error.
got3 := struct{ X S }{}
err := LoadStruct(&got3, []Property{{Name: "X"}})
if err == nil {
t.Error("got nil, want error")
}
}
// var got2 struct{ S []Pet }
// if err := LoadStruct(&got2, []Property{{Name: "S", Value: nil}}); err != nil {
// t.Fatal(err)
// }
// }

129
vendor/cloud.google.com/go/datastore/mutation.go generated vendored Normal file
View File

@ -0,0 +1,129 @@
// Copyright 2018 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 datastore
import (
"fmt"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
// A Mutation represents a change to a Datastore entity.
type Mutation struct {
key *Key // needed for transaction PendingKeys and to dedup deletions
mut *pb.Mutation
err error
}
func (m *Mutation) isDelete() bool {
_, ok := m.mut.Operation.(*pb.Mutation_Delete)
return ok
}
// NewInsert creates a mutation that will save the entity src into the datastore with
// key k, returning an error if k already exists.
// See Client.Put for valid values of src.
func NewInsert(k *Key, src interface{}) *Mutation {
if !k.valid() {
return &Mutation{err: ErrInvalidKey}
}
p, err := saveEntity(k, src)
if err != nil {
return &Mutation{err: err}
}
return &Mutation{
key: k,
mut: &pb.Mutation{Operation: &pb.Mutation_Insert{Insert: p}},
}
}
// NewUpsert creates a mutation that saves the entity src into the datastore with key
// k, whether or not k exists. See Client.Put for valid values of src.
func NewUpsert(k *Key, src interface{}) *Mutation {
if !k.valid() {
return &Mutation{err: ErrInvalidKey}
}
p, err := saveEntity(k, src)
if err != nil {
return &Mutation{err: err}
}
return &Mutation{
key: k,
mut: &pb.Mutation{Operation: &pb.Mutation_Upsert{Upsert: p}},
}
}
// NewUpdate creates a mutation that replaces the entity in the datastore with key k,
// returning an error if k does not exist. See Client.Put for valid values of src.
func NewUpdate(k *Key, src interface{}) *Mutation {
if !k.valid() {
return &Mutation{err: ErrInvalidKey}
}
if k.Incomplete() {
return &Mutation{err: fmt.Errorf("datastore: can't update the incomplete key: %v", k)}
}
p, err := saveEntity(k, src)
if err != nil {
return &Mutation{err: err}
}
return &Mutation{
key: k,
mut: &pb.Mutation{Operation: &pb.Mutation_Update{Update: p}},
}
}
// NewDelete creates a mutation that deletes the entity with key k.
func NewDelete(k *Key) *Mutation {
if !k.valid() {
return &Mutation{err: ErrInvalidKey}
}
if k.Incomplete() {
return &Mutation{err: fmt.Errorf("datastore: can't delete the incomplete key: %v", k)}
}
return &Mutation{
key: k,
mut: &pb.Mutation{Operation: &pb.Mutation_Delete{Delete: keyToProto(k)}},
}
}
func mutationProtos(muts []*Mutation) ([]*pb.Mutation, error) {
// If any of the mutations have errors, collect and return them.
var merr MultiError
for i, m := range muts {
if m.err != nil {
if merr == nil {
merr = make(MultiError, len(muts))
}
merr[i] = m.err
}
}
if merr != nil {
return nil, merr
}
var protos []*pb.Mutation
// Collect protos. Remove duplicate deletions (see deleteMutations).
seen := map[string]bool{}
for _, m := range muts {
if m.isDelete() {
ks := m.key.String()
if seen[ks] {
continue
}
seen[ks] = true
}
protos = append(protos, m.mut)
}
return protos, nil
}

150
vendor/cloud.google.com/go/datastore/mutation_test.go generated vendored Normal file
View File

@ -0,0 +1,150 @@
// Copyright 2018 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 datastore
import (
"testing"
"cloud.google.com/go/internal/testutil"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
func TestMutationProtos(t *testing.T) {
var keys []*Key
for i := 1; i <= 4; i++ {
k := IDKey("kind", int64(i), nil)
keys = append(keys, k)
}
entity := &PropertyList{{Name: "n", Value: "v"}}
entityForKey := func(k *Key) *pb.Entity {
return &pb.Entity{
Key: keyToProto(k),
Properties: map[string]*pb.Value{
"n": &pb.Value{ValueType: &pb.Value_StringValue{StringValue: "v"}},
},
}
}
for _, test := range []struct {
desc string
in []*Mutation
want []*pb.Mutation
}{
{
desc: "nil",
in: nil,
want: nil,
},
{
desc: "empty",
in: []*Mutation{},
want: nil,
},
{
desc: "various",
in: []*Mutation{
NewInsert(keys[0], entity),
NewUpsert(keys[1], entity),
NewUpdate(keys[2], entity),
NewDelete(keys[3]),
},
want: []*pb.Mutation{
&pb.Mutation{Operation: &pb.Mutation_Insert{Insert: entityForKey(keys[0])}},
&pb.Mutation{Operation: &pb.Mutation_Upsert{Upsert: entityForKey(keys[1])}},
&pb.Mutation{Operation: &pb.Mutation_Update{Update: entityForKey(keys[2])}},
&pb.Mutation{Operation: &pb.Mutation_Delete{Delete: keyToProto(keys[3])}},
},
},
{
desc: "duplicate deletes",
in: []*Mutation{
NewDelete(keys[0]),
NewInsert(keys[1], entity),
NewDelete(keys[0]),
NewDelete(keys[2]),
NewDelete(keys[0]),
},
want: []*pb.Mutation{
&pb.Mutation{Operation: &pb.Mutation_Delete{Delete: keyToProto(keys[0])}},
&pb.Mutation{Operation: &pb.Mutation_Insert{Insert: entityForKey(keys[1])}},
&pb.Mutation{Operation: &pb.Mutation_Delete{Delete: keyToProto(keys[2])}},
},
},
} {
got, err := mutationProtos(test.in)
if err != nil {
t.Errorf("%s: %v", test.desc, err)
continue
}
if diff := testutil.Diff(got, test.want); diff != "" {
t.Errorf("%s: %s", test.desc, diff)
}
}
}
func TestMutationProtosErrors(t *testing.T) {
entity := &PropertyList{{Name: "n", Value: "v"}}
k := IDKey("kind", 1, nil)
ik := IncompleteKey("kind", nil)
for _, test := range []struct {
desc string
in []*Mutation
want []int // non-nil indexes of MultiError
}{
{
desc: "invalid key",
in: []*Mutation{
NewInsert(nil, entity),
NewUpdate(nil, entity),
NewUpsert(nil, entity),
NewDelete(nil),
},
want: []int{0, 1, 2, 3},
},
{
desc: "incomplete key",
in: []*Mutation{
NewInsert(ik, entity),
NewUpdate(ik, entity),
NewUpsert(ik, entity),
NewDelete(ik),
},
want: []int{1, 3},
},
{
desc: "bad entity",
in: []*Mutation{
NewInsert(k, 1),
NewUpdate(k, 2),
NewUpsert(k, 3),
},
want: []int{0, 1, 2},
},
} {
_, err := mutationProtos(test.in)
if err == nil {
t.Errorf("%s: got nil, want error", test.desc)
continue
}
var got []int
for i, err := range err.(MultiError) {
if err != nil {
got = append(got, i)
}
}
if !testutil.Equal(got, test.want) {
t.Errorf("%s: got errors at %v, want at %v", test.desc, got, test.want)
}
}
}

59
vendor/cloud.google.com/go/datastore/oc_test.go generated vendored Normal file
View File

@ -0,0 +1,59 @@
// Copyright 2018 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.
// +build go1.8
package datastore
import (
"testing"
"go.opencensus.io/trace"
"golang.org/x/net/context"
)
func TestOCTracing(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
te := &testExporter{}
trace.RegisterExporter(te)
defer trace.UnregisterExporter(te)
trace.SetDefaultSampler(trace.AlwaysSample())
ctx := context.Background()
client := newTestClient(ctx, t)
defer client.Close()
type SomeValue struct {
S string
}
_, err := client.Put(ctx, IncompleteKey("SomeKey", nil), &SomeValue{"foo"})
if err != nil {
t.Fatalf("client.Put: %v", err)
}
if len(te.spans) != 1 {
t.Fatalf("Expected 1 span to be created, but got %d", len(te.spans))
}
}
type testExporter struct {
spans []*trace.SpanData
}
func (te *testExporter) ExportSpan(s *trace.SpanData) {
te.spans = append(te.spans, s)
}

View File

@ -23,6 +23,7 @@ import (
"strconv"
"strings"
"cloud.google.com/go/internal/trace"
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
@ -445,7 +446,10 @@ func (q *Query) toProto(req *pb.RunQueryRequest) error {
// with the sum of the query's offset and limit. Unless the result count is
// expected to be small, it is best to specify a limit; otherwise Count will
// continue until it finishes counting or the provided context expires.
func (c *Client) Count(ctx context.Context, q *Query) (int, error) {
func (c *Client) Count(ctx context.Context, q *Query) (_ int, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Query.Count")
defer func() { trace.EndSpan(ctx, err) }()
// Check that the query is well-formed.
if q.err != nil {
return 0, q.err
@ -492,7 +496,10 @@ func (c *Client) Count(ctx context.Context, q *Query) (int, error) {
// expected to be small, it is best to specify a limit; otherwise GetAll will
// continue until it finishes collecting results or the provided context
// expires.
func (c *Client) GetAll(ctx context.Context, q *Query, dst interface{}) ([]*Key, error) {
func (c *Client) GetAll(ctx context.Context, q *Query, dst interface{}) (_ []*Key, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Query.GetAll")
defer func() { trace.EndSpan(ctx, err) }()
var (
dv reflect.Value
mat multiArgType
@ -575,6 +582,9 @@ func (c *Client) Run(ctx context.Context, q *Query) *Iterator {
ProjectId: c.dataset,
},
}
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Query.Run")
defer func() { trace.EndSpan(ctx, t.err) }()
if q.namespace != "" {
t.req.PartitionId = &pb.PartitionId{
NamespaceId: q.namespace,
@ -622,7 +632,7 @@ type Iterator struct {
// If the query is not keys only and dst is non-nil, it also loads the entity
// stored for that key into the struct pointer or PropertyLoadSaver dst, with
// the same semantics and possible errors as for the Get function.
func (t *Iterator) Next(dst interface{}) (*Key, error) {
func (t *Iterator) Next(dst interface{}) (_ *Key, err error) {
k, e, err := t.next()
if err != nil {
return nil, err
@ -725,7 +735,10 @@ func (t *Iterator) nextBatch() error {
}
// Cursor returns a cursor for the iterator's current location.
func (t *Iterator) Cursor() (Cursor, error) {
func (t *Iterator) Cursor() (_ Cursor, err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Query.Cursor")
defer func() { trace.EndSpan(t.ctx, err) }()
// If there is still an offset, we need to the skip those results first.
for t.err == nil && t.offset > 0 {
t.err = t.nextBatch()

View File

@ -88,9 +88,19 @@ func saveStructProperty(props *[]Property, name string, opts saveOpts, v reflect
return saveSliceProperty(props, name, opts, v)
}
case reflect.Ptr:
if isValidPointerType(v.Type().Elem()) {
if v.IsNil() {
// Nil pointer becomes a nil property value (unless omitempty, handled above).
p.Value = nil
*props = append(*props, p)
return nil
}
return saveStructProperty(props, name, opts, v.Elem())
}
if v.Type().Elem().Kind() != reflect.Struct {
return fmt.Errorf("datastore: unsupported struct field type: %s", v.Type())
}
// Pointer to struct is a special case.
if v.IsNil() {
return nil
}
@ -395,10 +405,18 @@ func interfaceToProto(iv interface{}, noIndex bool) (*pb.Value, error) {
// than the top-level value.
val.ExcludeFromIndexes = false
default:
if iv != nil {
return nil, fmt.Errorf("invalid Value type %t", iv)
rv := reflect.ValueOf(iv)
if !rv.IsValid() {
val.ValueType = &pb.Value_NullValue{}
} else if rv.Kind() == reflect.Ptr { // non-nil pointer: dereference
if rv.IsNil() {
val.ValueType = &pb.Value_NullValue{}
return val, nil
}
return interfaceToProto(rv.Elem().Interface(), noIndex)
} else {
return nil, fmt.Errorf("invalid Value type %T", iv)
}
val.ValueType = &pb.Value_NullValue{}
}
// TODO(jbd): Support EntityValue.
return val, nil
@ -423,3 +441,22 @@ func isEmptyValue(v reflect.Value) bool {
}
return false
}
// isValidPointerType reports whether a struct field can be a pointer to type t
// for the purposes of saving and loading.
func isValidPointerType(t reflect.Type) bool {
if t == typeOfTime || t == typeOfGeoPoint {
return true
}
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
case reflect.Bool:
return true
case reflect.String:
return true
case reflect.Float32, reflect.Float64:
return true
}
return false
}

View File

@ -16,22 +16,32 @@ package datastore
import (
"testing"
"time"
"cloud.google.com/go/internal/testutil"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
func TestInterfaceToProtoNilKey(t *testing.T) {
var iv *Key
pv, err := interfaceToProto(iv, false)
if err != nil {
t.Fatalf("nil key: interfaceToProto: %v", err)
}
_, ok := pv.ValueType.(*pb.Value_NullValue)
if !ok {
t.Errorf("nil key: type:\ngot: %T\nwant: %T", pv.ValueType, &pb.Value_NullValue{})
func TestInterfaceToProtoNil(t *testing.T) {
// A nil *Key, or a nil value of any other pointer type, should convert to a NullValue.
for _, in := range []interface{}{
(*Key)(nil),
(*int)(nil),
(*string)(nil),
(*bool)(nil),
(*float64)(nil),
(*GeoPoint)(nil),
(*time.Time)(nil),
} {
got, err := interfaceToProto(in, false)
if err != nil {
t.Fatalf("%T: %v", in, err)
}
_, ok := got.ValueType.(*pb.Value_NullValue)
if !ok {
t.Errorf("%T: got: %T\nwant: %T", in, got.ValueType, &pb.Value_NullValue{})
}
}
}
@ -193,3 +203,83 @@ func TestSaveEntityNested(t *testing.T) {
}
}
}
func TestSavePointers(t *testing.T) {
for _, test := range []struct {
desc string
in interface{}
want []Property
}{
{
desc: "nil pointers save as nil-valued properties",
in: &Pointers{},
want: []Property{
Property{Name: "Pi", Value: nil},
Property{Name: "Ps", Value: nil},
Property{Name: "Pb", Value: nil},
Property{Name: "Pf", Value: nil},
Property{Name: "Pg", Value: nil},
Property{Name: "Pt", Value: nil},
},
},
{
desc: "nil omitempty pointers not saved",
in: &PointersOmitEmpty{},
want: []Property(nil),
},
{
desc: "non-nil zero-valued pointers save as zero values",
in: populatedPointers(),
want: []Property{
Property{Name: "Pi", Value: int64(0)},
Property{Name: "Ps", Value: ""},
Property{Name: "Pb", Value: false},
Property{Name: "Pf", Value: 0.0},
Property{Name: "Pg", Value: GeoPoint{}},
Property{Name: "Pt", Value: time.Time{}},
},
},
{
desc: "non-nil non-zero-valued pointers save as the appropriate values",
in: func() *Pointers {
p := populatedPointers()
*p.Pi = 1
*p.Ps = "x"
*p.Pb = true
*p.Pf = 3.14
*p.Pg = GeoPoint{Lat: 1, Lng: 2}
*p.Pt = time.Unix(100, 0)
return p
}(),
want: []Property{
Property{Name: "Pi", Value: int64(1)},
Property{Name: "Ps", Value: "x"},
Property{Name: "Pb", Value: true},
Property{Name: "Pf", Value: 3.14},
Property{Name: "Pg", Value: GeoPoint{Lat: 1, Lng: 2}},
Property{Name: "Pt", Value: time.Unix(100, 0)},
},
},
} {
got, err := SaveStruct(test.in)
if err != nil {
t.Fatalf("%s: %v", test.desc, err)
}
if !testutil.Equal(got, test.want) {
t.Errorf("%s\ngot %#v\nwant %#v\n", test.desc, got, test.want)
}
}
}
func TestSaveEmptySlice(t *testing.T) {
// Zero-length slice fields are not saved.
for _, slice := range [][]string{nil, {}} {
got, err := SaveStruct(&struct{ S []string }{S: slice})
if err != nil {
t.Fatal(err)
}
if len(got) != 0 {
t.Errorf("%#v: got %d properties, wanted zero", slice, len(got))
}
}
}

View File

@ -17,6 +17,7 @@ package datastore
import (
"errors"
"cloud.google.com/go/internal/trace"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
@ -32,6 +33,8 @@ var errExpiredTransaction = errors.New("datastore: transaction expired")
type transactionSettings struct {
attempts int
readOnly bool
prevID []byte // ID of the transaction to retry
}
// newTransactionSettings creates a transactionSettings with a given TransactionOption slice.
@ -62,6 +65,19 @@ func (w maxAttempts) apply(s *transactionSettings) {
}
}
// ReadOnly is a TransactionOption that marks the transaction as read-only.
var ReadOnly TransactionOption
func init() {
ReadOnly = readOnly{}
}
type readOnly struct{}
func (readOnly) apply(s *transactionSettings) {
s.readOnly = true
}
// Transaction represents a set of datastore operations to be committed atomically.
//
// Operations are enqueued by calling the Put and Delete methods on Transaction
@ -80,20 +96,35 @@ type Transaction struct {
}
// NewTransaction starts a new transaction.
func (c *Client) NewTransaction(ctx context.Context, opts ...TransactionOption) (*Transaction, error) {
func (c *Client) NewTransaction(ctx context.Context, opts ...TransactionOption) (_ *Transaction, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.NewTransaction")
defer func() { trace.EndSpan(ctx, err) }()
for _, o := range opts {
if _, ok := o.(maxAttempts); ok {
return nil, errors.New("datastore: NewTransaction does not accept MaxAttempts option")
}
}
req := &pb.BeginTransactionRequest{
ProjectId: c.dataset,
return c.newTransaction(ctx, newTransactionSettings(opts))
}
func (c *Client) newTransaction(ctx context.Context, s *transactionSettings) (*Transaction, error) {
req := &pb.BeginTransactionRequest{ProjectId: c.dataset}
if s.readOnly {
req.TransactionOptions = &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadOnly_{ReadOnly: &pb.TransactionOptions_ReadOnly{}},
}
} else if s.prevID != nil {
req.TransactionOptions = &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadWrite_{ReadWrite: &pb.TransactionOptions_ReadWrite{
PreviousTransaction: s.prevID,
}},
}
}
resp, err := c.client.BeginTransaction(ctx, req)
if err != nil {
return nil, err
}
return &Transaction{
id: resp.Transaction,
ctx: ctx,
@ -125,10 +156,13 @@ func (c *Client) NewTransaction(ctx context.Context, opts ...TransactionOption)
// is, it should have the same result when called multiple times. Note that
// Transaction.Get will append when unmarshalling slice fields, so it is not
// necessarily idempotent.
func (c *Client) RunInTransaction(ctx context.Context, f func(tx *Transaction) error, opts ...TransactionOption) (*Commit, error) {
func (c *Client) RunInTransaction(ctx context.Context, f func(tx *Transaction) error, opts ...TransactionOption) (_ *Commit, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.RunInTransaction")
defer func() { trace.EndSpan(ctx, err) }()
settings := newTransactionSettings(opts)
for n := 0; n < settings.attempts; n++ {
tx, err := c.NewTransaction(ctx)
tx, err := c.newTransaction(ctx, settings)
if err != nil {
return nil, err
}
@ -139,12 +173,20 @@ func (c *Client) RunInTransaction(ctx context.Context, f func(tx *Transaction) e
if cmt, err := tx.Commit(); err != ErrConcurrentTransaction {
return cmt, err
}
// Pass this transaction's ID to the retry transaction to preserve
// transaction priority.
if !settings.readOnly {
settings.prevID = tx.id
}
}
return nil, ErrConcurrentTransaction
}
// Commit applies the enqueued operations atomically.
func (t *Transaction) Commit() (*Commit, error) {
func (t *Transaction) Commit() (_ *Commit, err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Commit")
defer func() { trace.EndSpan(t.ctx, err) }()
if t.id == nil {
return nil, errExpiredTransaction
}
@ -181,13 +223,16 @@ func (t *Transaction) Commit() (*Commit, error) {
}
// Rollback abandons a pending transaction.
func (t *Transaction) Rollback() error {
func (t *Transaction) Rollback() (err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Rollback")
defer func() { trace.EndSpan(t.ctx, err) }()
if t.id == nil {
return errExpiredTransaction
}
id := t.id
t.id = nil
_, err := t.client.client.Rollback(t.ctx, &pb.RollbackRequest{
_, err = t.client.client.Rollback(t.ctx, &pb.RollbackRequest{
ProjectId: t.client.dataset,
Transaction: id,
})
@ -199,11 +244,14 @@ func (t *Transaction) Rollback() error {
// snapshot. Furthermore, if the transaction is set to a serializable isolation
// level, another transaction cannot concurrently modify the data that is read
// or modified by this transaction.
func (t *Transaction) Get(key *Key, dst interface{}) error {
func (t *Transaction) Get(key *Key, dst interface{}) (err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Get")
defer func() { trace.EndSpan(t.ctx, err) }()
opts := &pb.ReadOptions{
ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id},
}
err := t.client.get(t.ctx, []*Key{key}, []interface{}{dst}, opts)
err = t.client.get(t.ctx, []*Key{key}, []interface{}{dst}, opts)
if me, ok := err.(MultiError); ok {
return me[0]
}
@ -211,7 +259,10 @@ func (t *Transaction) Get(key *Key, dst interface{}) error {
}
// GetMulti is a batch version of Get.
func (t *Transaction) GetMulti(keys []*Key, dst interface{}) error {
func (t *Transaction) GetMulti(keys []*Key, dst interface{}) (err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.GetMulti")
defer func() { trace.EndSpan(t.ctx, err) }()
if t.id == nil {
return errExpiredTransaction
}
@ -240,7 +291,11 @@ func (t *Transaction) Put(key *Key, src interface{}) (*PendingKey, error) {
// PutMulti is a batch version of Put. One PendingKey is returned for each
// element of src in the same order.
func (t *Transaction) PutMulti(keys []*Key, src interface{}) ([]*PendingKey, error) {
// TODO(jba): rewrite in terms of Mutate.
func (t *Transaction) PutMulti(keys []*Key, src interface{}) (_ []*PendingKey, err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.PutMulti")
defer func() { trace.EndSpan(t.ctx, err) }()
if t.id == nil {
return nil, errExpiredTransaction
}
@ -279,7 +334,11 @@ func (t *Transaction) Delete(key *Key) error {
}
// DeleteMulti is a batch version of Delete.
func (t *Transaction) DeleteMulti(keys []*Key) error {
// TODO(jba): rewrite in terms of Mutate.
func (t *Transaction) DeleteMulti(keys []*Key) (err error) {
t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.DeleteMulti")
defer func() { trace.EndSpan(t.ctx, err) }()
if t.id == nil {
return errExpiredTransaction
}
@ -291,12 +350,53 @@ func (t *Transaction) DeleteMulti(keys []*Key) error {
return nil
}
// Mutate adds the mutations to the transaction. They will all be applied atomically
// upon calling Commit. Mutate returns a PendingKey for each Mutation in the argument
// list, in the same order. PendingKeys for Delete mutations are always nil.
//
// If any of the mutations are invalid, Mutate returns a MultiError with the errors.
// Mutate returns a MultiError in this case even if there is only one Mutation.
//
// For an example, see Client.Mutate.
func (t *Transaction) Mutate(muts ...*Mutation) ([]*PendingKey, error) {
if t.id == nil {
return nil, errExpiredTransaction
}
pmuts, err := mutationProtos(muts)
if err != nil {
return nil, err
}
origin := len(t.mutations)
t.mutations = append(t.mutations, pmuts...)
// Prepare the returned handles, pre-populating where possible.
ret := make([]*PendingKey, len(muts))
for i, mut := range muts {
if mut.isDelete() {
continue
}
p := &PendingKey{}
if mut.key.Incomplete() {
// This key will be in the final commit result.
t.pending[origin+i] = p
} else {
p.key = mut.key
}
ret[i] = p
}
return ret, nil
}
// Commit represents the result of a committed transaction.
type Commit struct{}
// Key resolves a pending key handle into a final key.
func (c *Commit) Key(p *PendingKey) *Key {
if c != p.commit {
if p == nil { // if called on a *PendingKey from a Delete mutation
return nil
}
// If p.commit is nil, the PendingKey did not come from an incomplete key,
// so p.key is valid.
if p.commit != nil && c != p.commit {
panic("PendingKey was not created by corresponding transaction")
}
return p.key

View File

@ -0,0 +1,78 @@
// Copyright 2017 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 datastore
import (
"testing"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
pb "google.golang.org/genproto/googleapis/datastore/v1"
)
func TestNewTransaction(t *testing.T) {
var got *pb.BeginTransactionRequest
client := &Client{
dataset: "project",
client: &fakeDatastoreClient{
beginTransaction: func(req *pb.BeginTransactionRequest) (*pb.BeginTransactionResponse, error) {
got = req
return &pb.BeginTransactionResponse{
Transaction: []byte("tid"),
}, nil
},
},
}
ctx := context.Background()
for _, test := range []struct {
settings *transactionSettings
want *pb.BeginTransactionRequest
}{
{
&transactionSettings{},
&pb.BeginTransactionRequest{ProjectId: "project"},
},
{
&transactionSettings{readOnly: true},
&pb.BeginTransactionRequest{
ProjectId: "project",
TransactionOptions: &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadOnly_{ReadOnly: &pb.TransactionOptions_ReadOnly{}},
},
},
},
{
&transactionSettings{prevID: []byte("tid")},
&pb.BeginTransactionRequest{
ProjectId: "project",
TransactionOptions: &pb.TransactionOptions{
Mode: &pb.TransactionOptions_ReadWrite_{ReadWrite: &pb.TransactionOptions_ReadWrite{
PreviousTransaction: []byte("tid"),
},
},
},
},
},
} {
_, err := client.newTransaction(ctx, test.settings)
if err != nil {
t.Fatal(err)
}
if !proto.Equal(got, test.want) {
t.Errorf("%+v:\ngot %+v\nwant %+v", test.settings, got, test.want)
}
}
}

810
vendor/cloud.google.com/go/dlp/apiv2/dlp_client.go generated vendored Normal file
View File

@ -0,0 +1,810 @@
// 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 dlp
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"
dlppb "google.golang.org/genproto/googleapis/privacy/dlp/v2"
"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 {
InspectContent []gax.CallOption
RedactImage []gax.CallOption
DeidentifyContent []gax.CallOption
ReidentifyContent []gax.CallOption
ListInfoTypes []gax.CallOption
CreateInspectTemplate []gax.CallOption
UpdateInspectTemplate []gax.CallOption
GetInspectTemplate []gax.CallOption
ListInspectTemplates []gax.CallOption
DeleteInspectTemplate []gax.CallOption
CreateDeidentifyTemplate []gax.CallOption
UpdateDeidentifyTemplate []gax.CallOption
GetDeidentifyTemplate []gax.CallOption
ListDeidentifyTemplates []gax.CallOption
DeleteDeidentifyTemplate []gax.CallOption
CreateDlpJob []gax.CallOption
ListDlpJobs []gax.CallOption
GetDlpJob []gax.CallOption
DeleteDlpJob []gax.CallOption
CancelDlpJob []gax.CallOption
ListJobTriggers []gax.CallOption
GetJobTrigger []gax.CallOption
DeleteJobTrigger []gax.CallOption
UpdateJobTrigger []gax.CallOption
CreateJobTrigger []gax.CallOption
}
func defaultClientOptions() []option.ClientOption {
return []option.ClientOption{
option.WithEndpoint("dlp.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{
InspectContent: retry[[2]string{"default", "idempotent"}],
RedactImage: retry[[2]string{"default", "idempotent"}],
DeidentifyContent: retry[[2]string{"default", "idempotent"}],
ReidentifyContent: retry[[2]string{"default", "idempotent"}],
ListInfoTypes: retry[[2]string{"default", "idempotent"}],
CreateInspectTemplate: retry[[2]string{"default", "non_idempotent"}],
UpdateInspectTemplate: retry[[2]string{"default", "non_idempotent"}],
GetInspectTemplate: retry[[2]string{"default", "idempotent"}],
ListInspectTemplates: retry[[2]string{"default", "idempotent"}],
DeleteInspectTemplate: retry[[2]string{"default", "idempotent"}],
CreateDeidentifyTemplate: retry[[2]string{"default", "non_idempotent"}],
UpdateDeidentifyTemplate: retry[[2]string{"default", "non_idempotent"}],
GetDeidentifyTemplate: retry[[2]string{"default", "idempotent"}],
ListDeidentifyTemplates: retry[[2]string{"default", "idempotent"}],
DeleteDeidentifyTemplate: retry[[2]string{"default", "idempotent"}],
CreateDlpJob: retry[[2]string{"default", "non_idempotent"}],
ListDlpJobs: retry[[2]string{"default", "idempotent"}],
GetDlpJob: retry[[2]string{"default", "idempotent"}],
DeleteDlpJob: retry[[2]string{"default", "idempotent"}],
CancelDlpJob: retry[[2]string{"default", "non_idempotent"}],
ListJobTriggers: retry[[2]string{"default", "idempotent"}],
GetJobTrigger: retry[[2]string{"default", "idempotent"}],
DeleteJobTrigger: retry[[2]string{"default", "idempotent"}],
UpdateJobTrigger: retry[[2]string{"default", "non_idempotent"}],
CreateJobTrigger: retry[[2]string{"default", "non_idempotent"}],
}
}
// Client is a client for interacting with DLP API.
type Client struct {
// The connection to the service.
conn *grpc.ClientConn
// The gRPC API client.
client dlppb.DlpServiceClient
// 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 dlp service client.
//
// The DLP API is a service that allows clients
// to detect the presence of Personally Identifiable Information (PII) and other
// privacy-sensitive data in user-supplied, unstructured data streams, like text
// blocks or images.
// The service also includes methods for sensitive data redaction and
// scheduling of data scans on Google Cloud Platform based data sets.
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: dlppb.NewDlpServiceClient(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...))
}
// InspectContent finds potentially sensitive info in content.
// This method has limits on input size, processing time, and output size.
// How-to guide for text (at /dlp/docs/inspecting-text), How-to guide for
// images (at /dlp/docs/inspecting-images)
func (c *Client) InspectContent(ctx context.Context, req *dlppb.InspectContentRequest, opts ...gax.CallOption) (*dlppb.InspectContentResponse, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.InspectContent[0:len(c.CallOptions.InspectContent):len(c.CallOptions.InspectContent)], opts...)
var resp *dlppb.InspectContentResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.InspectContent(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// RedactImage redacts potentially sensitive info from an image.
// This method has limits on input size, processing time, and output size.
// How-to guide (at /dlp/docs/redacting-sensitive-data-images)
func (c *Client) RedactImage(ctx context.Context, req *dlppb.RedactImageRequest, opts ...gax.CallOption) (*dlppb.RedactImageResponse, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.RedactImage[0:len(c.CallOptions.RedactImage):len(c.CallOptions.RedactImage)], opts...)
var resp *dlppb.RedactImageResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.RedactImage(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// DeidentifyContent de-identifies potentially sensitive info from a ContentItem.
// This method has limits on input size and output size.
// How-to guide (at /dlp/docs/deidentify-sensitive-data)
func (c *Client) DeidentifyContent(ctx context.Context, req *dlppb.DeidentifyContentRequest, opts ...gax.CallOption) (*dlppb.DeidentifyContentResponse, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.DeidentifyContent[0:len(c.CallOptions.DeidentifyContent):len(c.CallOptions.DeidentifyContent)], opts...)
var resp *dlppb.DeidentifyContentResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.DeidentifyContent(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ReidentifyContent re-identify content that has been de-identified.
func (c *Client) ReidentifyContent(ctx context.Context, req *dlppb.ReidentifyContentRequest, opts ...gax.CallOption) (*dlppb.ReidentifyContentResponse, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.ReidentifyContent[0:len(c.CallOptions.ReidentifyContent):len(c.CallOptions.ReidentifyContent)], opts...)
var resp *dlppb.ReidentifyContentResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.ReidentifyContent(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ListInfoTypes returns sensitive information types DLP supports.
func (c *Client) ListInfoTypes(ctx context.Context, req *dlppb.ListInfoTypesRequest, opts ...gax.CallOption) (*dlppb.ListInfoTypesResponse, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.ListInfoTypes[0:len(c.CallOptions.ListInfoTypes):len(c.CallOptions.ListInfoTypes)], opts...)
var resp *dlppb.ListInfoTypesResponse
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.ListInfoTypes(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// CreateInspectTemplate creates an inspect template for re-using frequently used configuration
// for inspecting content, images, and storage.
func (c *Client) CreateInspectTemplate(ctx context.Context, req *dlppb.CreateInspectTemplateRequest, opts ...gax.CallOption) (*dlppb.InspectTemplate, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.CreateInspectTemplate[0:len(c.CallOptions.CreateInspectTemplate):len(c.CallOptions.CreateInspectTemplate)], opts...)
var resp *dlppb.InspectTemplate
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.CreateInspectTemplate(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// UpdateInspectTemplate updates the inspect template.
func (c *Client) UpdateInspectTemplate(ctx context.Context, req *dlppb.UpdateInspectTemplateRequest, opts ...gax.CallOption) (*dlppb.InspectTemplate, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.UpdateInspectTemplate[0:len(c.CallOptions.UpdateInspectTemplate):len(c.CallOptions.UpdateInspectTemplate)], opts...)
var resp *dlppb.InspectTemplate
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.UpdateInspectTemplate(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// GetInspectTemplate gets an inspect template.
func (c *Client) GetInspectTemplate(ctx context.Context, req *dlppb.GetInspectTemplateRequest, opts ...gax.CallOption) (*dlppb.InspectTemplate, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.GetInspectTemplate[0:len(c.CallOptions.GetInspectTemplate):len(c.CallOptions.GetInspectTemplate)], opts...)
var resp *dlppb.InspectTemplate
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.GetInspectTemplate(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ListInspectTemplates lists inspect templates.
func (c *Client) ListInspectTemplates(ctx context.Context, req *dlppb.ListInspectTemplatesRequest, opts ...gax.CallOption) *InspectTemplateIterator {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.ListInspectTemplates[0:len(c.CallOptions.ListInspectTemplates):len(c.CallOptions.ListInspectTemplates)], opts...)
it := &InspectTemplateIterator{}
it.InternalFetch = func(pageSize int, pageToken string) ([]*dlppb.InspectTemplate, string, error) {
var resp *dlppb.ListInspectTemplatesResponse
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.ListInspectTemplates(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, "", err
}
return resp.InspectTemplates, 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
}
// DeleteInspectTemplate deletes inspect templates.
func (c *Client) DeleteInspectTemplate(ctx context.Context, req *dlppb.DeleteInspectTemplateRequest, opts ...gax.CallOption) error {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.DeleteInspectTemplate[0:len(c.CallOptions.DeleteInspectTemplate):len(c.CallOptions.DeleteInspectTemplate)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.client.DeleteInspectTemplate(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// CreateDeidentifyTemplate creates an Deidentify template for re-using frequently used configuration
// for Deidentifying content, images, and storage.
func (c *Client) CreateDeidentifyTemplate(ctx context.Context, req *dlppb.CreateDeidentifyTemplateRequest, opts ...gax.CallOption) (*dlppb.DeidentifyTemplate, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.CreateDeidentifyTemplate[0:len(c.CallOptions.CreateDeidentifyTemplate):len(c.CallOptions.CreateDeidentifyTemplate)], opts...)
var resp *dlppb.DeidentifyTemplate
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.CreateDeidentifyTemplate(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// UpdateDeidentifyTemplate updates the inspect template.
func (c *Client) UpdateDeidentifyTemplate(ctx context.Context, req *dlppb.UpdateDeidentifyTemplateRequest, opts ...gax.CallOption) (*dlppb.DeidentifyTemplate, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.UpdateDeidentifyTemplate[0:len(c.CallOptions.UpdateDeidentifyTemplate):len(c.CallOptions.UpdateDeidentifyTemplate)], opts...)
var resp *dlppb.DeidentifyTemplate
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.UpdateDeidentifyTemplate(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// GetDeidentifyTemplate gets an inspect template.
func (c *Client) GetDeidentifyTemplate(ctx context.Context, req *dlppb.GetDeidentifyTemplateRequest, opts ...gax.CallOption) (*dlppb.DeidentifyTemplate, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.GetDeidentifyTemplate[0:len(c.CallOptions.GetDeidentifyTemplate):len(c.CallOptions.GetDeidentifyTemplate)], opts...)
var resp *dlppb.DeidentifyTemplate
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.GetDeidentifyTemplate(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ListDeidentifyTemplates lists inspect templates.
func (c *Client) ListDeidentifyTemplates(ctx context.Context, req *dlppb.ListDeidentifyTemplatesRequest, opts ...gax.CallOption) *DeidentifyTemplateIterator {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.ListDeidentifyTemplates[0:len(c.CallOptions.ListDeidentifyTemplates):len(c.CallOptions.ListDeidentifyTemplates)], opts...)
it := &DeidentifyTemplateIterator{}
it.InternalFetch = func(pageSize int, pageToken string) ([]*dlppb.DeidentifyTemplate, string, error) {
var resp *dlppb.ListDeidentifyTemplatesResponse
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.ListDeidentifyTemplates(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, "", err
}
return resp.DeidentifyTemplates, 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
}
// DeleteDeidentifyTemplate deletes inspect templates.
func (c *Client) DeleteDeidentifyTemplate(ctx context.Context, req *dlppb.DeleteDeidentifyTemplateRequest, opts ...gax.CallOption) error {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.DeleteDeidentifyTemplate[0:len(c.CallOptions.DeleteDeidentifyTemplate):len(c.CallOptions.DeleteDeidentifyTemplate)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.client.DeleteDeidentifyTemplate(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// CreateDlpJob create a new job to inspect storage or calculate risk metrics How-to
// guide (at /dlp/docs/compute-risk-analysis).
func (c *Client) CreateDlpJob(ctx context.Context, req *dlppb.CreateDlpJobRequest, opts ...gax.CallOption) (*dlppb.DlpJob, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.CreateDlpJob[0:len(c.CallOptions.CreateDlpJob):len(c.CallOptions.CreateDlpJob)], opts...)
var resp *dlppb.DlpJob
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.CreateDlpJob(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// ListDlpJobs lists DlpJobs that match the specified filter in the request.
func (c *Client) ListDlpJobs(ctx context.Context, req *dlppb.ListDlpJobsRequest, opts ...gax.CallOption) *DlpJobIterator {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.ListDlpJobs[0:len(c.CallOptions.ListDlpJobs):len(c.CallOptions.ListDlpJobs)], opts...)
it := &DlpJobIterator{}
it.InternalFetch = func(pageSize int, pageToken string) ([]*dlppb.DlpJob, string, error) {
var resp *dlppb.ListDlpJobsResponse
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.ListDlpJobs(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, "", err
}
return resp.Jobs, 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
}
// GetDlpJob gets the latest state of a long-running DlpJob.
func (c *Client) GetDlpJob(ctx context.Context, req *dlppb.GetDlpJobRequest, opts ...gax.CallOption) (*dlppb.DlpJob, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.GetDlpJob[0:len(c.CallOptions.GetDlpJob):len(c.CallOptions.GetDlpJob)], opts...)
var resp *dlppb.DlpJob
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.GetDlpJob(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// DeleteDlpJob deletes a long-running DlpJob. This method indicates that the client is
// no longer interested in the DlpJob result. The job will be cancelled if
// possible.
func (c *Client) DeleteDlpJob(ctx context.Context, req *dlppb.DeleteDlpJobRequest, opts ...gax.CallOption) error {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.DeleteDlpJob[0:len(c.CallOptions.DeleteDlpJob):len(c.CallOptions.DeleteDlpJob)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.client.DeleteDlpJob(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// CancelDlpJob starts asynchronous cancellation on a long-running DlpJob. The server
// makes a best effort to cancel the DlpJob, but success is not
// guaranteed.
func (c *Client) CancelDlpJob(ctx context.Context, req *dlppb.CancelDlpJobRequest, opts ...gax.CallOption) error {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.CancelDlpJob[0:len(c.CallOptions.CancelDlpJob):len(c.CallOptions.CancelDlpJob)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.client.CancelDlpJob(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// ListJobTriggers lists job triggers.
func (c *Client) ListJobTriggers(ctx context.Context, req *dlppb.ListJobTriggersRequest, opts ...gax.CallOption) *JobTriggerIterator {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.ListJobTriggers[0:len(c.CallOptions.ListJobTriggers):len(c.CallOptions.ListJobTriggers)], opts...)
it := &JobTriggerIterator{}
it.InternalFetch = func(pageSize int, pageToken string) ([]*dlppb.JobTrigger, string, error) {
var resp *dlppb.ListJobTriggersResponse
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.ListJobTriggers(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, "", err
}
return resp.JobTriggers, 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
}
// GetJobTrigger gets a job trigger.
func (c *Client) GetJobTrigger(ctx context.Context, req *dlppb.GetJobTriggerRequest, opts ...gax.CallOption) (*dlppb.JobTrigger, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.GetJobTrigger[0:len(c.CallOptions.GetJobTrigger):len(c.CallOptions.GetJobTrigger)], opts...)
var resp *dlppb.JobTrigger
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.GetJobTrigger(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// DeleteJobTrigger deletes a job trigger.
func (c *Client) DeleteJobTrigger(ctx context.Context, req *dlppb.DeleteJobTriggerRequest, opts ...gax.CallOption) error {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.DeleteJobTrigger[0:len(c.CallOptions.DeleteJobTrigger):len(c.CallOptions.DeleteJobTrigger)], opts...)
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
_, err = c.client.DeleteJobTrigger(ctx, req, settings.GRPC...)
return err
}, opts...)
return err
}
// UpdateJobTrigger updates a job trigger.
func (c *Client) UpdateJobTrigger(ctx context.Context, req *dlppb.UpdateJobTriggerRequest, opts ...gax.CallOption) (*dlppb.JobTrigger, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.UpdateJobTrigger[0:len(c.CallOptions.UpdateJobTrigger):len(c.CallOptions.UpdateJobTrigger)], opts...)
var resp *dlppb.JobTrigger
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.UpdateJobTrigger(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// CreateJobTrigger creates a job to run DLP actions such as scanning storage for sensitive
// information on a set schedule.
func (c *Client) CreateJobTrigger(ctx context.Context, req *dlppb.CreateJobTriggerRequest, opts ...gax.CallOption) (*dlppb.JobTrigger, error) {
ctx = insertMetadata(ctx, c.xGoogMetadata)
opts = append(c.CallOptions.CreateJobTrigger[0:len(c.CallOptions.CreateJobTrigger):len(c.CallOptions.CreateJobTrigger)], opts...)
var resp *dlppb.JobTrigger
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
var err error
resp, err = c.client.CreateJobTrigger(ctx, req, settings.GRPC...)
return err
}, opts...)
if err != nil {
return nil, err
}
return resp, nil
}
// DeidentifyTemplateIterator manages a stream of *dlppb.DeidentifyTemplate.
type DeidentifyTemplateIterator struct {
items []*dlppb.DeidentifyTemplate
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 []*dlppb.DeidentifyTemplate, nextPageToken string, err error)
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *DeidentifyTemplateIterator) 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 *DeidentifyTemplateIterator) Next() (*dlppb.DeidentifyTemplate, error) {
var item *dlppb.DeidentifyTemplate
if err := it.nextFunc(); err != nil {
return item, err
}
item = it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *DeidentifyTemplateIterator) bufLen() int {
return len(it.items)
}
func (it *DeidentifyTemplateIterator) takeBuf() interface{} {
b := it.items
it.items = nil
return b
}
// DlpJobIterator manages a stream of *dlppb.DlpJob.
type DlpJobIterator struct {
items []*dlppb.DlpJob
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 []*dlppb.DlpJob, nextPageToken string, err error)
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *DlpJobIterator) 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 *DlpJobIterator) Next() (*dlppb.DlpJob, error) {
var item *dlppb.DlpJob
if err := it.nextFunc(); err != nil {
return item, err
}
item = it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *DlpJobIterator) bufLen() int {
return len(it.items)
}
func (it *DlpJobIterator) takeBuf() interface{} {
b := it.items
it.items = nil
return b
}
// InspectTemplateIterator manages a stream of *dlppb.InspectTemplate.
type InspectTemplateIterator struct {
items []*dlppb.InspectTemplate
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 []*dlppb.InspectTemplate, nextPageToken string, err error)
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *InspectTemplateIterator) 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 *InspectTemplateIterator) Next() (*dlppb.InspectTemplate, error) {
var item *dlppb.InspectTemplate
if err := it.nextFunc(); err != nil {
return item, err
}
item = it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *InspectTemplateIterator) bufLen() int {
return len(it.items)
}
func (it *InspectTemplateIterator) takeBuf() interface{} {
b := it.items
it.items = nil
return b
}
// JobTriggerIterator manages a stream of *dlppb.JobTrigger.
type JobTriggerIterator struct {
items []*dlppb.JobTrigger
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 []*dlppb.JobTrigger, nextPageToken string, err error)
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *JobTriggerIterator) 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 *JobTriggerIterator) Next() (*dlppb.JobTrigger, error) {
var item *dlppb.JobTrigger
if err := it.nextFunc(); err != nil {
return item, err
}
item = it.items[0]
it.items = it.items[1:]
return item, nil
}
func (it *JobTriggerIterator) bufLen() int {
return len(it.items)
}
func (it *JobTriggerIterator) takeBuf() interface{} {
b := it.items
it.items = nil
return b
}

View File

@ -0,0 +1,498 @@
// 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 dlp_test
import (
"cloud.google.com/go/dlp/apiv2"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
dlppb "google.golang.org/genproto/googleapis/privacy/dlp/v2"
)
func ExampleNewClient() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
// TODO: Use client.
_ = c
}
func ExampleClient_InspectContent() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.InspectContentRequest{
// TODO: Fill request struct fields.
}
resp, err := c.InspectContent(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_RedactImage() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.RedactImageRequest{
// TODO: Fill request struct fields.
}
resp, err := c.RedactImage(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_DeidentifyContent() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.DeidentifyContentRequest{
// TODO: Fill request struct fields.
}
resp, err := c.DeidentifyContent(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_ReidentifyContent() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.ReidentifyContentRequest{
// TODO: Fill request struct fields.
}
resp, err := c.ReidentifyContent(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_ListInfoTypes() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.ListInfoTypesRequest{
// TODO: Fill request struct fields.
}
resp, err := c.ListInfoTypes(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_CreateInspectTemplate() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.CreateInspectTemplateRequest{
// TODO: Fill request struct fields.
}
resp, err := c.CreateInspectTemplate(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_UpdateInspectTemplate() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.UpdateInspectTemplateRequest{
// TODO: Fill request struct fields.
}
resp, err := c.UpdateInspectTemplate(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_GetInspectTemplate() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.GetInspectTemplateRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetInspectTemplate(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_ListInspectTemplates() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.ListInspectTemplatesRequest{
// TODO: Fill request struct fields.
}
it := c.ListInspectTemplates(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_DeleteInspectTemplate() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.DeleteInspectTemplateRequest{
// TODO: Fill request struct fields.
}
err = c.DeleteInspectTemplate(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_CreateDeidentifyTemplate() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.CreateDeidentifyTemplateRequest{
// TODO: Fill request struct fields.
}
resp, err := c.CreateDeidentifyTemplate(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_UpdateDeidentifyTemplate() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.UpdateDeidentifyTemplateRequest{
// TODO: Fill request struct fields.
}
resp, err := c.UpdateDeidentifyTemplate(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_GetDeidentifyTemplate() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.GetDeidentifyTemplateRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetDeidentifyTemplate(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_ListDeidentifyTemplates() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.ListDeidentifyTemplatesRequest{
// TODO: Fill request struct fields.
}
it := c.ListDeidentifyTemplates(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_DeleteDeidentifyTemplate() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.DeleteDeidentifyTemplateRequest{
// TODO: Fill request struct fields.
}
err = c.DeleteDeidentifyTemplate(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_CreateDlpJob() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.CreateDlpJobRequest{
// TODO: Fill request struct fields.
}
resp, err := c.CreateDlpJob(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_ListDlpJobs() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.ListDlpJobsRequest{
// TODO: Fill request struct fields.
}
it := c.ListDlpJobs(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_GetDlpJob() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.GetDlpJobRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetDlpJob(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_DeleteDlpJob() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.DeleteDlpJobRequest{
// TODO: Fill request struct fields.
}
err = c.DeleteDlpJob(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_CancelDlpJob() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.CancelDlpJobRequest{
// TODO: Fill request struct fields.
}
err = c.CancelDlpJob(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_ListJobTriggers() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.ListJobTriggersRequest{
// TODO: Fill request struct fields.
}
it := c.ListJobTriggers(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
}
func ExampleClient_GetJobTrigger() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.GetJobTriggerRequest{
// TODO: Fill request struct fields.
}
resp, err := c.GetJobTrigger(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_DeleteJobTrigger() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.DeleteJobTriggerRequest{
// TODO: Fill request struct fields.
}
err = c.DeleteJobTrigger(ctx, req)
if err != nil {
// TODO: Handle error.
}
}
func ExampleClient_UpdateJobTrigger() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.UpdateJobTriggerRequest{
// TODO: Fill request struct fields.
}
resp, err := c.UpdateJobTrigger(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}
func ExampleClient_CreateJobTrigger() {
ctx := context.Background()
c, err := dlp.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
req := &dlppb.CreateJobTriggerRequest{
// TODO: Fill request struct fields.
}
resp, err := c.CreateJobTrigger(ctx, req)
if err != nil {
// TODO: Handle error.
}
// TODO: Use resp.
_ = resp
}

45
vendor/cloud.google.com/go/dlp/apiv2/doc.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
// 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 dlp is an auto-generated package for the
// DLP API.
//
// NOTE: This package is in alpha. It is not stable, and is likely to change.
//
package dlp // import "cloud.google.com/go/dlp/apiv2"
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/cloud-platform",
}
}

1902
vendor/cloud.google.com/go/dlp/apiv2/mock_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -69,13 +69,13 @@ func TestReportErrorsServiceSmoke(t *testing.T) {
LineNumber: lineNumber,
FunctionName: functionName,
}
var context = &clouderrorreportingpb.ErrorContext{
var context_ = &clouderrorreportingpb.ErrorContext{
ReportLocation: reportLocation,
}
var event = &clouderrorreportingpb.ReportedErrorEvent{
Message: message,
ServiceContext: serviceContext,
Context: context,
Context: context_,
}
var request = &clouderrorreportingpb.ReportErrorEventRequest{
ProjectName: formattedProjectName,

13
vendor/cloud.google.com/go/firestore/Makefile generated vendored Normal file
View File

@ -0,0 +1,13 @@
# Copy textproto files in this directory from the source of truth.
SRC=$(GOPATH)/src/github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore
.PHONY: refresh-tests
refresh-tests:
-rm genproto/*.pb.go
cp $(SRC)/genproto/*.pb.go genproto
-rm testdata/*.textproto
cp $(SRC)/testdata/*.textproto testdata
openssl dgst -sha1 $(SRC)/testdata/test-suite.binproto > testdata/VERSION

View File

@ -29,6 +29,7 @@ import (
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/ptypes"
tspb "github.com/golang/protobuf/ptypes/timestamp"
"golang.org/x/net/context"
"google.golang.org/api/option"
"google.golang.org/grpc/metadata"
@ -128,6 +129,10 @@ func (c *Client) GetAll(ctx context.Context, docRefs []*DocumentRef) ([]*Documen
if err := checkTransaction(ctx); err != nil {
return nil, err
}
return c.getAll(ctx, docRefs, nil)
}
func (c *Client) getAll(ctx context.Context, docRefs []*DocumentRef, tid []byte) ([]*DocumentSnapshot, error) {
var docNames []string
for _, dr := range docRefs {
if dr == nil {
@ -139,13 +144,21 @@ func (c *Client) GetAll(ctx context.Context, docRefs []*DocumentRef) ([]*Documen
Database: c.path(),
Documents: docNames,
}
if tid != nil {
req.ConsistencySelector = &pb.BatchGetDocumentsRequest_Transaction{tid}
}
streamClient, err := c.c.BatchGetDocuments(withResourceHeader(ctx, req.Database), req)
if err != nil {
return nil, err
}
// Read results from the stream and add them to a map.
docMap := map[string]*pb.Document{}
type result struct {
doc *pb.Document
readTime *tspb.Timestamp
}
docMap := map[string]result{}
for {
res, err := streamClient.Recv()
if err == io.EOF {
@ -156,13 +169,13 @@ func (c *Client) GetAll(ctx context.Context, docRefs []*DocumentRef) ([]*Documen
}
switch x := res.Result.(type) {
case *pb.BatchGetDocumentsResponse_Found:
docMap[x.Found.Name] = x.Found
docMap[x.Found.Name] = result{x.Found, res.ReadTime}
case *pb.BatchGetDocumentsResponse_Missing:
if docMap[x.Missing] != nil {
return nil, fmt.Errorf("firestore: %q both missing and present", x.Missing)
if _, ok := docMap[x.Missing]; ok {
return nil, fmt.Errorf("firestore: %q seen twice", x.Missing)
}
docMap[x.Missing] = nil
docMap[x.Missing] = result{nil, res.ReadTime}
default:
return nil, errors.New("firestore: unknown BatchGetDocumentsResponse result type")
}
@ -172,12 +185,12 @@ func (c *Client) GetAll(ctx context.Context, docRefs []*DocumentRef) ([]*Documen
// DocumentRefs.
docs := make([]*DocumentSnapshot, len(docNames))
for i, name := range docNames {
pbDoc, ok := docMap[name]
r, ok := docMap[name]
if !ok {
return nil, fmt.Errorf("firestore: passed %q to BatchGetDocuments but never saw response", name)
}
if pbDoc != nil {
doc, err := newDocumentSnapshot(docRefs[i], pbDoc, c)
if r.doc != nil {
doc, err := newDocumentSnapshot(docRefs[i], r.doc, c, r.readTime)
if err != nil {
return nil, err
}

View File

@ -17,10 +17,11 @@ package firestore
import (
"testing"
tspb "github.com/golang/protobuf/ptypes/timestamp"
"golang.org/x/net/context"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var testClient = &Client{
@ -92,10 +93,23 @@ func TestClientCollDocErrors(t *testing.T) {
}
func TestGetAll(t *testing.T) {
ctx := context.Background()
const dbPath = "projects/projectID/databases/(default)"
c, srv := newMock(t)
defer c.Close()
const dbPath = "projects/projectID/databases/(default)"
req := &pb.BatchGetDocumentsRequest{
Database: dbPath,
Documents: []string{
dbPath + "/documents/C/a",
dbPath + "/documents/C/b",
dbPath + "/documents/C/c",
},
}
testGetAll(t, c, srv, dbPath, func(drs []*DocumentRef) ([]*DocumentSnapshot, error) {
return c.GetAll(context.Background(), drs)
}, req)
}
func testGetAll(t *testing.T, c *Client, srv *mockServer, dbPath string, getAll func([]*DocumentRef) ([]*DocumentSnapshot, error), req *pb.BatchGetDocumentsRequest) {
wantPBDocs := []*pb.Document{
{
Name: dbPath + "/documents/C/a",
@ -111,25 +125,21 @@ func TestGetAll(t *testing.T) {
Fields: map[string]*pb.Value{"f": intval(1)},
},
}
srv.addRPC(
&pb.BatchGetDocumentsRequest{
Database: dbPath,
Documents: []string{
dbPath + "/documents/C/a",
dbPath + "/documents/C/b",
dbPath + "/documents/C/c",
},
},
wantReadTimes := []*tspb.Timestamp{aTimestamp, aTimestamp2, aTimestamp3}
srv.addRPC(req,
[]interface{}{
// deliberately put these out of order
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Found{wantPBDocs[2]},
Result: &pb.BatchGetDocumentsResponse_Found{wantPBDocs[2]},
ReadTime: aTimestamp3,
},
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Found{wantPBDocs[0]},
Result: &pb.BatchGetDocumentsResponse_Found{wantPBDocs[0]},
ReadTime: aTimestamp,
},
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Missing{dbPath + "/documents/C/b"},
Result: &pb.BatchGetDocumentsResponse_Missing{dbPath + "/documents/C/b"},
ReadTime: aTimestamp2,
},
},
)
@ -138,7 +148,7 @@ func TestGetAll(t *testing.T) {
for _, name := range []string{"a", "b", "c"} {
docRefs = append(docRefs, coll.Doc(name))
}
docs, err := c.GetAll(ctx, docRefs)
docs, err := getAll(docRefs)
if err != nil {
t.Fatal(err)
}
@ -148,7 +158,7 @@ func TestGetAll(t *testing.T) {
for i, got := range docs {
var want *DocumentSnapshot
if wantPBDocs[i] != nil {
want, err = newDocumentSnapshot(docRefs[i], wantPBDocs[i], c)
want, err = newDocumentSnapshot(docRefs[i], wantPBDocs[i], c, wantReadTimes[i])
if err != nil {
t.Fatal(err)
}
@ -176,7 +186,7 @@ func TestGetAllErrors(t *testing.T) {
Database: dbPath,
Documents: []string{docPath},
},
[]interface{}{grpc.Errorf(codes.Internal, "")},
[]interface{}{status.Errorf(codes.Internal, "")},
)
_, err := c.GetAll(ctx, []*DocumentRef{c.Doc("C/a")})
codeEq(t, "GetAll #1", codes.Internal, err)
@ -190,10 +200,12 @@ func TestGetAllErrors(t *testing.T) {
},
[]interface{}{
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Found{&pb.Document{Name: docPath}},
Result: &pb.BatchGetDocumentsResponse_Found{&pb.Document{Name: docPath}},
ReadTime: aTimestamp,
},
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Missing{docPath},
Result: &pb.BatchGetDocumentsResponse_Missing{docPath},
ReadTime: aTimestamp,
},
},
)

View File

@ -18,6 +18,7 @@ package firestore
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"path"
@ -63,22 +64,34 @@ func runTestFromFile(t *testing.T, filename string) {
}
func runTest(t *testing.T, msg string, test *pb.Test) {
check := func(gotErr error, wantErr bool) {
check := func(gotErr error, wantErr bool) bool {
if wantErr && gotErr == nil {
t.Errorf("%s: got nil, want error", msg)
return false
} else if !wantErr && gotErr != nil {
t.Errorf("%s: %v", msg, gotErr)
return false
}
return true
}
ctx := context.Background()
c, srv := newMock(t)
switch tt := test.Test.(type) {
case *pb.Test_Get:
srv.addRPC(tt.Get.Request, &fspb.Document{
CreateTime: &ts.Timestamp{},
UpdateTime: &ts.Timestamp{},
req := &fspb.BatchGetDocumentsRequest{
Database: c.path(),
Documents: []string{tt.Get.DocRefPath},
}
srv.addRPC(req, []interface{}{
&fspb.BatchGetDocumentsResponse{
Result: &fspb.BatchGetDocumentsResponse_Found{&fspb.Document{
Name: tt.Get.DocRefPath,
CreateTime: &ts.Timestamp{},
UpdateTime: &ts.Timestamp{},
}},
ReadTime: &ts.Timestamp{},
},
})
ref := docRefFromPath(tt.Get.DocRefPath, c)
_, err := ref.Get(ctx)
@ -126,14 +139,13 @@ func runTest(t *testing.T, msg string, test *pb.Test) {
paths := convertFieldPaths(tt.UpdatePaths.FieldPaths)
var ups []Update
for i, path := range paths {
jsonValue := tt.UpdatePaths.JsonValues[i]
var val interface{}
if err := json.Unmarshal([]byte(jsonValue), &val); err != nil {
t.Fatalf("%s: %q: %v", msg, jsonValue, err)
val, err := convertJSONValue(tt.UpdatePaths.JsonValues[i])
if err != nil {
t.Fatalf("%s: %v", msg, err)
}
ups = append(ups, Update{
FieldPath: path,
Value: convertTestValue(val),
Value: val,
})
}
_, err := ref.Update(ctx, ups, preconds...)
@ -146,6 +158,15 @@ func runTest(t *testing.T, msg string, test *pb.Test) {
_, err := ref.Delete(ctx, preconds...)
check(err, tt.Delete.IsError)
case *pb.Test_Query:
q := convertQuery(t, tt.Query)
got, err := q.toProto()
if check(err, tt.Query.IsError) && err == nil {
if want := tt.Query.Query; !proto.Equal(got, want) {
t.Errorf("%s\ngot: %s\nwant: %s", msg, proto.MarshalTextString(got), proto.MarshalTextString(want))
}
}
default:
t.Fatalf("unknown test type %T", tt)
}
@ -159,6 +180,14 @@ func docRefFromPath(p string, c *Client) *DocumentRef {
}
}
func convertJSONValue(jv string) (interface{}, error) {
var val interface{}
if err := json.Unmarshal([]byte(jv), &val); err != nil {
return nil, err
}
return convertTestValue(val), nil
}
func convertData(jsonData string) (map[string]interface{}, error) {
var m map[string]interface{}
if err := json.Unmarshal([]byte(jsonData), &m); err != nil {
@ -236,3 +265,90 @@ func convertPrecondition(t *testing.T, fp *fspb.Precondition) []Precondition {
}
return []Precondition{pc}
}
func convertQuery(t *testing.T, qt *pb.QueryTest) Query {
parts := strings.Split(qt.CollPath, "/")
q := Query{
parentPath: strings.Join(parts[:len(parts)-2], "/"),
collectionID: parts[len(parts)-1],
}
for _, c := range qt.Clauses {
switch c := c.Clause.(type) {
case *pb.Clause_Select:
q = q.SelectPaths(convertFieldPaths(c.Select.Fields)...)
case *pb.Clause_OrderBy:
var dir Direction
switch c.OrderBy.Direction {
case "asc":
dir = Asc
case "desc":
dir = Desc
default:
t.Fatalf("bad direction: %q", c.OrderBy.Direction)
}
q = q.OrderByPath(FieldPath(c.OrderBy.Path.Field), dir)
case *pb.Clause_Where:
val, err := convertJSONValue(c.Where.JsonValue)
if err != nil {
t.Fatal(err)
}
q = q.WherePath(FieldPath(c.Where.Path.Field), c.Where.Op, val)
case *pb.Clause_Offset:
q = q.Offset(int(c.Offset))
case *pb.Clause_Limit:
q = q.Limit(int(c.Limit))
case *pb.Clause_StartAt:
q = q.StartAt(convertCursor(t, c.StartAt)...)
case *pb.Clause_StartAfter:
q = q.StartAfter(convertCursor(t, c.StartAfter)...)
case *pb.Clause_EndAt:
q = q.EndAt(convertCursor(t, c.EndAt)...)
case *pb.Clause_EndBefore:
q = q.EndBefore(convertCursor(t, c.EndBefore)...)
default:
t.Fatalf("bad clause type %T", c)
}
}
return q
}
// Returns args to a cursor method (StartAt, etc.).
func convertCursor(t *testing.T, c *pb.Cursor) []interface{} {
if c.DocSnapshot != nil {
ds, err := convertDocSnapshot(c.DocSnapshot)
if err != nil {
t.Fatal(err)
}
return []interface{}{ds}
}
var vals []interface{}
for _, jv := range c.JsonValues {
v, err := convertJSONValue(jv)
if err != nil {
t.Fatal(err)
}
vals = append(vals, v)
}
return vals
}
func convertDocSnapshot(ds *pb.DocSnapshot) (*DocumentSnapshot, error) {
data, err := convertData(ds.JsonData)
if err != nil {
return nil, err
}
doc, transformPaths, err := toProtoDocument(data)
if err != nil {
return nil, err
}
if len(transformPaths) > 0 {
return nil, errors.New("saw transform paths in DocSnapshot")
}
return &DocumentSnapshot{
Ref: &DocumentRef{
Path: ds.Path,
Parent: &CollectionRef{Path: path.Dir(ds.Path)},
},
proto: doc,
}, nil
}

View File

@ -21,6 +21,9 @@ database.
See https://cloud.google.com/firestore/docs for an introduction
to Cloud Firestore and additional help on using the Firestore API.
Note: you can't use both Cloud Firestore and Cloud Datastore in the same
project.
Creating a Client
To start working with this package, create a client with a project ID:

View File

@ -22,6 +22,8 @@ import (
"golang.org/x/net/context"
"google.golang.org/api/iterator"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
vkit "cloud.google.com/go/firestore/apiv1beta1"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
@ -64,12 +66,14 @@ func (d *DocumentRef) Get(ctx context.Context) (*DocumentSnapshot, error) {
if d == nil {
return nil, errNilDocRef
}
doc, err := d.Parent.c.c.GetDocument(withResourceHeader(ctx, d.Parent.c.path()),
&pb.GetDocumentRequest{Name: d.Path})
docsnaps, err := d.Parent.c.getAll(ctx, []*DocumentRef{d}, nil)
if err != nil {
return nil, err
}
return newDocumentSnapshot(d, doc, d.Parent.c)
if docsnaps[0] == nil {
return nil, status.Errorf(codes.NotFound, "%q not found", d.Path)
}
return docsnaps[0], nil
}
// Create creates the document with the given data.

View File

@ -45,7 +45,15 @@ func TestDocGet(t *testing.T) {
UpdateTime: aTimestamp,
Fields: map[string]*pb.Value{"f": intval(1)},
}
srv.addRPC(&pb.GetDocumentRequest{Name: path}, pdoc)
srv.addRPC(&pb.BatchGetDocumentsRequest{
Database: c.path(),
Documents: []string{path},
}, []interface{}{
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Found{pdoc},
ReadTime: aTimestamp2,
},
})
ref := c.Collection("C").Doc("a")
gotDoc, err := ref.Get(ctx)
if err != nil {
@ -55,6 +63,7 @@ func TestDocGet(t *testing.T) {
Ref: ref,
CreateTime: aTime,
UpdateTime: aTime,
ReadTime: aTime2,
proto: pdoc,
c: c,
}
@ -62,12 +71,17 @@ func TestDocGet(t *testing.T) {
t.Fatalf("\ngot %+v\nwant %+v", gotDoc, wantDoc)
}
path2 := "projects/projectID/databases/(default)/documents/C/b"
srv.addRPC(
&pb.GetDocumentRequest{
Name: "projects/projectID/databases/(default)/documents/C/b",
},
grpc.Errorf(codes.NotFound, "not found"),
)
&pb.BatchGetDocumentsRequest{
Database: c.path(),
Documents: []string{path2},
}, []interface{}{
&pb.BatchGetDocumentsResponse{
Result: &pb.BatchGetDocumentsResponse_Missing{path2},
ReadTime: aTimestamp3,
},
})
_, err = c.Collection("C").Doc("b").Get(ctx)
if grpc.Code(err) != codes.NotFound {
t.Errorf("got %v, want NotFound", err)

View File

@ -23,6 +23,7 @@ import (
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"github.com/golang/protobuf/ptypes"
tspb "github.com/golang/protobuf/ptypes/timestamp"
)
// A DocumentSnapshot contains document data and metadata.
@ -42,6 +43,9 @@ type DocumentSnapshot struct {
// documents and the read time of a query.
UpdateTime time.Time
// Read-only. The time at which the document was read.
ReadTime time.Time
c *Client
proto *pb.Document
}
@ -241,7 +245,7 @@ func extractTransformPathsFromStruct(v reflect.Value, prefix FieldPath) ([]Field
return paths, nil
}
func newDocumentSnapshot(ref *DocumentRef, proto *pb.Document, c *Client) (*DocumentSnapshot, error) {
func newDocumentSnapshot(ref *DocumentRef, proto *pb.Document, c *Client, readTime *tspb.Timestamp) (*DocumentSnapshot, error) {
d := &DocumentSnapshot{
Ref: ref,
c: c,
@ -257,5 +261,13 @@ func newDocumentSnapshot(ref *DocumentRef, proto *pb.Document, c *Client) (*Docu
return nil, err
}
d.UpdateTime = ts
// TODO(jba): remove nil check when all callers pass a read time.
if readTime != nil {
ts, err = ptypes.Timestamp(readTime)
if err != nil {
return nil, err
}
d.ReadTime = ts
}
return d, nil
}

View File

@ -69,10 +69,11 @@ func TestNewDocumentSnapshot(t *testing.T) {
Ref: docRef,
CreateTime: time.Unix(10, 0).UTC(),
UpdateTime: time.Unix(20, 0).UTC(),
ReadTime: aTime,
proto: in,
c: c,
}
got, err := newDocumentSnapshot(docRef, in, c)
got, err := newDocumentSnapshot(docRef, in, c, aTimestamp)
if err != nil {
t.Fatal(err)
}

View File

@ -73,6 +73,14 @@ func setReflectFromProtoValue(v reflect.Value, vproto *pb.Value, c *Client) erro
v.Set(reflect.ValueOf(t))
return nil
case typeOfProtoTimestamp:
x, ok := val.(*pb.Value_TimestampValue)
if !ok {
return typeErr()
}
v.Set(reflect.ValueOf(x.TimestampValue))
return nil
case typeOfLatLng:
x, ok := val.(*pb.Value_GeoPointValue)
if !ok {

View File

@ -24,14 +24,16 @@ import (
"testing"
"time"
ts "github.com/golang/protobuf/ptypes/timestamp"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"google.golang.org/genproto/googleapis/type/latlng"
)
var (
tm = time.Date(2016, 12, 25, 0, 0, 0, 123456789, time.UTC)
ll = &latlng.LatLng{Latitude: 20, Longitude: 30}
tm = time.Date(2016, 12, 25, 0, 0, 0, 123456789, time.UTC)
ll = &latlng.LatLng{Latitude: 20, Longitude: 30}
ptm = &ts.Timestamp{12345, 67890}
)
func TestCreateFromProtoValue(t *testing.T) {
@ -187,6 +189,7 @@ func TestSetFromProtoValueNoJSON(t *testing.T) {
bs []byte
tmi time.Time
lli *latlng.LatLng
tmp *ts.Timestamp
)
bytes := []byte{1, 2, 3}
@ -197,6 +200,7 @@ func TestSetFromProtoValueNoJSON(t *testing.T) {
}{
{&bs, bytesval(bytes), bytes},
{&tmi, tsval(tm), tm},
{&tmp, &pb.Value{&pb.Value_TimestampValue{ptm}}, ptm},
{&lli, geoval(ll), ll},
} {
if err := setFromProtoValue(test.in, test.val, &Client{}); err != nil {

View File

@ -1,3 +0,0 @@
The contents of this directory are copied from
github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/genproto.

View File

@ -9,6 +9,7 @@ It is generated from these files:
test.proto
It has these top-level messages:
TestSuite
Test
GetTest
CreateTest
@ -17,6 +18,13 @@ It has these top-level messages:
UpdatePathsTest
DeleteTest
SetOption
QueryTest
Clause
Select
Where
OrderBy
Cursor
DocSnapshot
FieldPath
*/
package tests
@ -26,6 +34,7 @@ import fmt "fmt"
import math "math"
import google_firestore_v1beta14 "google.golang.org/genproto/googleapis/firestore/v1beta1"
import google_firestore_v1beta1 "google.golang.org/genproto/googleapis/firestore/v1beta1"
import google_firestore_v1beta12 "google.golang.org/genproto/googleapis/firestore/v1beta1"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@ -38,6 +47,23 @@ var _ = math.Inf
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// A collection of tests.
type TestSuite struct {
Tests []*Test `protobuf:"bytes,1,rep,name=tests" json:"tests,omitempty"`
}
func (m *TestSuite) Reset() { *m = TestSuite{} }
func (m *TestSuite) String() string { return proto.CompactTextString(m) }
func (*TestSuite) ProtoMessage() {}
func (*TestSuite) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *TestSuite) GetTests() []*Test {
if m != nil {
return m.Tests
}
return nil
}
// A Test describes a single client method call and its expected result.
type Test struct {
Description string `protobuf:"bytes,1,opt,name=description" json:"description,omitempty"`
@ -48,13 +74,14 @@ type Test struct {
// *Test_Update
// *Test_UpdatePaths
// *Test_Delete
// *Test_Query
Test isTest_Test `protobuf_oneof:"test"`
}
func (m *Test) Reset() { *m = Test{} }
func (m *Test) String() string { return proto.CompactTextString(m) }
func (*Test) ProtoMessage() {}
func (*Test) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (*Test) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
type isTest_Test interface {
isTest_Test()
@ -78,6 +105,9 @@ type Test_UpdatePaths struct {
type Test_Delete struct {
Delete *DeleteTest `protobuf:"bytes,7,opt,name=delete,oneof"`
}
type Test_Query struct {
Query *QueryTest `protobuf:"bytes,8,opt,name=query,oneof"`
}
func (*Test_Get) isTest_Test() {}
func (*Test_Create) isTest_Test() {}
@ -85,6 +115,7 @@ func (*Test_Set) isTest_Test() {}
func (*Test_Update) isTest_Test() {}
func (*Test_UpdatePaths) isTest_Test() {}
func (*Test_Delete) isTest_Test() {}
func (*Test_Query) isTest_Test() {}
func (m *Test) GetTest() isTest_Test {
if m != nil {
@ -142,6 +173,13 @@ func (m *Test) GetDelete() *DeleteTest {
return nil
}
func (m *Test) GetQuery() *QueryTest {
if x, ok := m.GetTest().(*Test_Query); ok {
return x.Query
}
return nil
}
// XXX_OneofFuncs is for the internal use of the proto package.
func (*Test) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
return _Test_OneofMarshaler, _Test_OneofUnmarshaler, _Test_OneofSizer, []interface{}{
@ -151,6 +189,7 @@ func (*Test) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, f
(*Test_Update)(nil),
(*Test_UpdatePaths)(nil),
(*Test_Delete)(nil),
(*Test_Query)(nil),
}
}
@ -188,6 +227,11 @@ func _Test_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
if err := b.EncodeMessage(x.Delete); err != nil {
return err
}
case *Test_Query:
b.EncodeVarint(8<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.Query); err != nil {
return err
}
case nil:
default:
return fmt.Errorf("Test.Test has unexpected type %T", x)
@ -246,6 +290,14 @@ func _Test_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (
err := b.DecodeMessage(msg)
m.Test = &Test_Delete{msg}
return true, err
case 8: // test.query
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(QueryTest)
err := b.DecodeMessage(msg)
m.Test = &Test_Query{msg}
return true, err
default:
return false, nil
}
@ -285,6 +337,11 @@ func _Test_OneofSizer(msg proto.Message) (n int) {
n += proto.SizeVarint(7<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case *Test_Query:
s := proto.Size(x.Query)
n += proto.SizeVarint(8<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case nil:
default:
panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
@ -303,7 +360,7 @@ type GetTest struct {
func (m *GetTest) Reset() { *m = GetTest{} }
func (m *GetTest) String() string { return proto.CompactTextString(m) }
func (*GetTest) ProtoMessage() {}
func (*GetTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (*GetTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (m *GetTest) GetDocRefPath() string {
if m != nil {
@ -337,7 +394,7 @@ type CreateTest struct {
func (m *CreateTest) Reset() { *m = CreateTest{} }
func (m *CreateTest) String() string { return proto.CompactTextString(m) }
func (*CreateTest) ProtoMessage() {}
func (*CreateTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (*CreateTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func (m *CreateTest) GetDocRefPath() string {
if m != nil {
@ -379,7 +436,7 @@ type SetTest struct {
func (m *SetTest) Reset() { *m = SetTest{} }
func (m *SetTest) String() string { return proto.CompactTextString(m) }
func (*SetTest) ProtoMessage() {}
func (*SetTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func (*SetTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
func (m *SetTest) GetDocRefPath() string {
if m != nil {
@ -429,7 +486,7 @@ type UpdateTest struct {
func (m *UpdateTest) Reset() { *m = UpdateTest{} }
func (m *UpdateTest) String() string { return proto.CompactTextString(m) }
func (*UpdateTest) ProtoMessage() {}
func (*UpdateTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
func (*UpdateTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
func (m *UpdateTest) GetDocRefPath() string {
if m != nil {
@ -481,7 +538,7 @@ type UpdatePathsTest struct {
func (m *UpdatePathsTest) Reset() { *m = UpdatePathsTest{} }
func (m *UpdatePathsTest) String() string { return proto.CompactTextString(m) }
func (*UpdatePathsTest) ProtoMessage() {}
func (*UpdatePathsTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
func (*UpdatePathsTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
func (m *UpdatePathsTest) GetDocRefPath() string {
if m != nil {
@ -536,7 +593,7 @@ type DeleteTest struct {
func (m *DeleteTest) Reset() { *m = DeleteTest{} }
func (m *DeleteTest) String() string { return proto.CompactTextString(m) }
func (*DeleteTest) ProtoMessage() {}
func (*DeleteTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
func (*DeleteTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
func (m *DeleteTest) GetDocRefPath() string {
if m != nil {
@ -575,7 +632,7 @@ type SetOption struct {
func (m *SetOption) Reset() { *m = SetOption{} }
func (m *SetOption) String() string { return proto.CompactTextString(m) }
func (*SetOption) ProtoMessage() {}
func (*SetOption) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
func (*SetOption) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
func (m *SetOption) GetAll() bool {
if m != nil {
@ -591,7 +648,495 @@ func (m *SetOption) GetFields() []*FieldPath {
return nil
}
// A field path.
type QueryTest struct {
CollPath string `protobuf:"bytes,1,opt,name=coll_path,json=collPath" json:"coll_path,omitempty"`
Clauses []*Clause `protobuf:"bytes,2,rep,name=clauses" json:"clauses,omitempty"`
Query *google_firestore_v1beta12.StructuredQuery `protobuf:"bytes,3,opt,name=query" json:"query,omitempty"`
IsError bool `protobuf:"varint,4,opt,name=is_error,json=isError" json:"is_error,omitempty"`
}
func (m *QueryTest) Reset() { *m = QueryTest{} }
func (m *QueryTest) String() string { return proto.CompactTextString(m) }
func (*QueryTest) ProtoMessage() {}
func (*QueryTest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
func (m *QueryTest) GetCollPath() string {
if m != nil {
return m.CollPath
}
return ""
}
func (m *QueryTest) GetClauses() []*Clause {
if m != nil {
return m.Clauses
}
return nil
}
func (m *QueryTest) GetQuery() *google_firestore_v1beta12.StructuredQuery {
if m != nil {
return m.Query
}
return nil
}
func (m *QueryTest) GetIsError() bool {
if m != nil {
return m.IsError
}
return false
}
type Clause struct {
// Types that are valid to be assigned to Clause:
// *Clause_Select
// *Clause_Where
// *Clause_OrderBy
// *Clause_Offset
// *Clause_Limit
// *Clause_StartAt
// *Clause_StartAfter
// *Clause_EndAt
// *Clause_EndBefore
Clause isClause_Clause `protobuf_oneof:"clause"`
}
func (m *Clause) Reset() { *m = Clause{} }
func (m *Clause) String() string { return proto.CompactTextString(m) }
func (*Clause) ProtoMessage() {}
func (*Clause) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} }
type isClause_Clause interface {
isClause_Clause()
}
type Clause_Select struct {
Select *Select `protobuf:"bytes,1,opt,name=select,oneof"`
}
type Clause_Where struct {
Where *Where `protobuf:"bytes,2,opt,name=where,oneof"`
}
type Clause_OrderBy struct {
OrderBy *OrderBy `protobuf:"bytes,3,opt,name=order_by,json=orderBy,oneof"`
}
type Clause_Offset struct {
Offset int32 `protobuf:"varint,4,opt,name=offset,oneof"`
}
type Clause_Limit struct {
Limit int32 `protobuf:"varint,5,opt,name=limit,oneof"`
}
type Clause_StartAt struct {
StartAt *Cursor `protobuf:"bytes,6,opt,name=start_at,json=startAt,oneof"`
}
type Clause_StartAfter struct {
StartAfter *Cursor `protobuf:"bytes,7,opt,name=start_after,json=startAfter,oneof"`
}
type Clause_EndAt struct {
EndAt *Cursor `protobuf:"bytes,8,opt,name=end_at,json=endAt,oneof"`
}
type Clause_EndBefore struct {
EndBefore *Cursor `protobuf:"bytes,9,opt,name=end_before,json=endBefore,oneof"`
}
func (*Clause_Select) isClause_Clause() {}
func (*Clause_Where) isClause_Clause() {}
func (*Clause_OrderBy) isClause_Clause() {}
func (*Clause_Offset) isClause_Clause() {}
func (*Clause_Limit) isClause_Clause() {}
func (*Clause_StartAt) isClause_Clause() {}
func (*Clause_StartAfter) isClause_Clause() {}
func (*Clause_EndAt) isClause_Clause() {}
func (*Clause_EndBefore) isClause_Clause() {}
func (m *Clause) GetClause() isClause_Clause {
if m != nil {
return m.Clause
}
return nil
}
func (m *Clause) GetSelect() *Select {
if x, ok := m.GetClause().(*Clause_Select); ok {
return x.Select
}
return nil
}
func (m *Clause) GetWhere() *Where {
if x, ok := m.GetClause().(*Clause_Where); ok {
return x.Where
}
return nil
}
func (m *Clause) GetOrderBy() *OrderBy {
if x, ok := m.GetClause().(*Clause_OrderBy); ok {
return x.OrderBy
}
return nil
}
func (m *Clause) GetOffset() int32 {
if x, ok := m.GetClause().(*Clause_Offset); ok {
return x.Offset
}
return 0
}
func (m *Clause) GetLimit() int32 {
if x, ok := m.GetClause().(*Clause_Limit); ok {
return x.Limit
}
return 0
}
func (m *Clause) GetStartAt() *Cursor {
if x, ok := m.GetClause().(*Clause_StartAt); ok {
return x.StartAt
}
return nil
}
func (m *Clause) GetStartAfter() *Cursor {
if x, ok := m.GetClause().(*Clause_StartAfter); ok {
return x.StartAfter
}
return nil
}
func (m *Clause) GetEndAt() *Cursor {
if x, ok := m.GetClause().(*Clause_EndAt); ok {
return x.EndAt
}
return nil
}
func (m *Clause) GetEndBefore() *Cursor {
if x, ok := m.GetClause().(*Clause_EndBefore); ok {
return x.EndBefore
}
return nil
}
// XXX_OneofFuncs is for the internal use of the proto package.
func (*Clause) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
return _Clause_OneofMarshaler, _Clause_OneofUnmarshaler, _Clause_OneofSizer, []interface{}{
(*Clause_Select)(nil),
(*Clause_Where)(nil),
(*Clause_OrderBy)(nil),
(*Clause_Offset)(nil),
(*Clause_Limit)(nil),
(*Clause_StartAt)(nil),
(*Clause_StartAfter)(nil),
(*Clause_EndAt)(nil),
(*Clause_EndBefore)(nil),
}
}
func _Clause_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
m := msg.(*Clause)
// clause
switch x := m.Clause.(type) {
case *Clause_Select:
b.EncodeVarint(1<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.Select); err != nil {
return err
}
case *Clause_Where:
b.EncodeVarint(2<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.Where); err != nil {
return err
}
case *Clause_OrderBy:
b.EncodeVarint(3<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.OrderBy); err != nil {
return err
}
case *Clause_Offset:
b.EncodeVarint(4<<3 | proto.WireVarint)
b.EncodeVarint(uint64(x.Offset))
case *Clause_Limit:
b.EncodeVarint(5<<3 | proto.WireVarint)
b.EncodeVarint(uint64(x.Limit))
case *Clause_StartAt:
b.EncodeVarint(6<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.StartAt); err != nil {
return err
}
case *Clause_StartAfter:
b.EncodeVarint(7<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.StartAfter); err != nil {
return err
}
case *Clause_EndAt:
b.EncodeVarint(8<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.EndAt); err != nil {
return err
}
case *Clause_EndBefore:
b.EncodeVarint(9<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.EndBefore); err != nil {
return err
}
case nil:
default:
return fmt.Errorf("Clause.Clause has unexpected type %T", x)
}
return nil
}
func _Clause_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
m := msg.(*Clause)
switch tag {
case 1: // clause.select
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(Select)
err := b.DecodeMessage(msg)
m.Clause = &Clause_Select{msg}
return true, err
case 2: // clause.where
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(Where)
err := b.DecodeMessage(msg)
m.Clause = &Clause_Where{msg}
return true, err
case 3: // clause.order_by
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(OrderBy)
err := b.DecodeMessage(msg)
m.Clause = &Clause_OrderBy{msg}
return true, err
case 4: // clause.offset
if wire != proto.WireVarint {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeVarint()
m.Clause = &Clause_Offset{int32(x)}
return true, err
case 5: // clause.limit
if wire != proto.WireVarint {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeVarint()
m.Clause = &Clause_Limit{int32(x)}
return true, err
case 6: // clause.start_at
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(Cursor)
err := b.DecodeMessage(msg)
m.Clause = &Clause_StartAt{msg}
return true, err
case 7: // clause.start_after
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(Cursor)
err := b.DecodeMessage(msg)
m.Clause = &Clause_StartAfter{msg}
return true, err
case 8: // clause.end_at
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(Cursor)
err := b.DecodeMessage(msg)
m.Clause = &Clause_EndAt{msg}
return true, err
case 9: // clause.end_before
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(Cursor)
err := b.DecodeMessage(msg)
m.Clause = &Clause_EndBefore{msg}
return true, err
default:
return false, nil
}
}
func _Clause_OneofSizer(msg proto.Message) (n int) {
m := msg.(*Clause)
// clause
switch x := m.Clause.(type) {
case *Clause_Select:
s := proto.Size(x.Select)
n += proto.SizeVarint(1<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case *Clause_Where:
s := proto.Size(x.Where)
n += proto.SizeVarint(2<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case *Clause_OrderBy:
s := proto.Size(x.OrderBy)
n += proto.SizeVarint(3<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case *Clause_Offset:
n += proto.SizeVarint(4<<3 | proto.WireVarint)
n += proto.SizeVarint(uint64(x.Offset))
case *Clause_Limit:
n += proto.SizeVarint(5<<3 | proto.WireVarint)
n += proto.SizeVarint(uint64(x.Limit))
case *Clause_StartAt:
s := proto.Size(x.StartAt)
n += proto.SizeVarint(6<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case *Clause_StartAfter:
s := proto.Size(x.StartAfter)
n += proto.SizeVarint(7<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case *Clause_EndAt:
s := proto.Size(x.EndAt)
n += proto.SizeVarint(8<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case *Clause_EndBefore:
s := proto.Size(x.EndBefore)
n += proto.SizeVarint(9<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case nil:
default:
panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
}
return n
}
type Select struct {
Fields []*FieldPath `protobuf:"bytes,1,rep,name=fields" json:"fields,omitempty"`
}
func (m *Select) Reset() { *m = Select{} }
func (m *Select) String() string { return proto.CompactTextString(m) }
func (*Select) ProtoMessage() {}
func (*Select) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} }
func (m *Select) GetFields() []*FieldPath {
if m != nil {
return m.Fields
}
return nil
}
type Where struct {
Path *FieldPath `protobuf:"bytes,1,opt,name=path" json:"path,omitempty"`
Op string `protobuf:"bytes,2,opt,name=op" json:"op,omitempty"`
JsonValue string `protobuf:"bytes,3,opt,name=json_value,json=jsonValue" json:"json_value,omitempty"`
}
func (m *Where) Reset() { *m = Where{} }
func (m *Where) String() string { return proto.CompactTextString(m) }
func (*Where) ProtoMessage() {}
func (*Where) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} }
func (m *Where) GetPath() *FieldPath {
if m != nil {
return m.Path
}
return nil
}
func (m *Where) GetOp() string {
if m != nil {
return m.Op
}
return ""
}
func (m *Where) GetJsonValue() string {
if m != nil {
return m.JsonValue
}
return ""
}
type OrderBy struct {
Path *FieldPath `protobuf:"bytes,1,opt,name=path" json:"path,omitempty"`
Direction string `protobuf:"bytes,2,opt,name=direction" json:"direction,omitempty"`
}
func (m *OrderBy) Reset() { *m = OrderBy{} }
func (m *OrderBy) String() string { return proto.CompactTextString(m) }
func (*OrderBy) ProtoMessage() {}
func (*OrderBy) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} }
func (m *OrderBy) GetPath() *FieldPath {
if m != nil {
return m.Path
}
return nil
}
func (m *OrderBy) GetDirection() string {
if m != nil {
return m.Direction
}
return ""
}
type Cursor struct {
// one of:
DocSnapshot *DocSnapshot `protobuf:"bytes,1,opt,name=doc_snapshot,json=docSnapshot" json:"doc_snapshot,omitempty"`
JsonValues []string `protobuf:"bytes,2,rep,name=json_values,json=jsonValues" json:"json_values,omitempty"`
}
func (m *Cursor) Reset() { *m = Cursor{} }
func (m *Cursor) String() string { return proto.CompactTextString(m) }
func (*Cursor) ProtoMessage() {}
func (*Cursor) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} }
func (m *Cursor) GetDocSnapshot() *DocSnapshot {
if m != nil {
return m.DocSnapshot
}
return nil
}
func (m *Cursor) GetJsonValues() []string {
if m != nil {
return m.JsonValues
}
return nil
}
type DocSnapshot struct {
Path string `protobuf:"bytes,1,opt,name=path" json:"path,omitempty"`
JsonData string `protobuf:"bytes,2,opt,name=json_data,json=jsonData" json:"json_data,omitempty"`
}
func (m *DocSnapshot) Reset() { *m = DocSnapshot{} }
func (m *DocSnapshot) String() string { return proto.CompactTextString(m) }
func (*DocSnapshot) ProtoMessage() {}
func (*DocSnapshot) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }
func (m *DocSnapshot) GetPath() string {
if m != nil {
return m.Path
}
return ""
}
func (m *DocSnapshot) GetJsonData() string {
if m != nil {
return m.JsonData
}
return ""
}
type FieldPath struct {
Field []string `protobuf:"bytes,1,rep,name=field" json:"field,omitempty"`
}
@ -599,7 +1144,7 @@ type FieldPath struct {
func (m *FieldPath) Reset() { *m = FieldPath{} }
func (m *FieldPath) String() string { return proto.CompactTextString(m) }
func (*FieldPath) ProtoMessage() {}
func (*FieldPath) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
func (*FieldPath) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} }
func (m *FieldPath) GetField() []string {
if m != nil {
@ -609,6 +1154,7 @@ func (m *FieldPath) GetField() []string {
}
func init() {
proto.RegisterType((*TestSuite)(nil), "tests.TestSuite")
proto.RegisterType((*Test)(nil), "tests.Test")
proto.RegisterType((*GetTest)(nil), "tests.GetTest")
proto.RegisterType((*CreateTest)(nil), "tests.CreateTest")
@ -617,46 +1163,81 @@ func init() {
proto.RegisterType((*UpdatePathsTest)(nil), "tests.UpdatePathsTest")
proto.RegisterType((*DeleteTest)(nil), "tests.DeleteTest")
proto.RegisterType((*SetOption)(nil), "tests.SetOption")
proto.RegisterType((*QueryTest)(nil), "tests.QueryTest")
proto.RegisterType((*Clause)(nil), "tests.Clause")
proto.RegisterType((*Select)(nil), "tests.Select")
proto.RegisterType((*Where)(nil), "tests.Where")
proto.RegisterType((*OrderBy)(nil), "tests.OrderBy")
proto.RegisterType((*Cursor)(nil), "tests.Cursor")
proto.RegisterType((*DocSnapshot)(nil), "tests.DocSnapshot")
proto.RegisterType((*FieldPath)(nil), "tests.FieldPath")
}
func init() { proto.RegisterFile("test.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 559 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xc4, 0x55, 0x4d, 0x6f, 0xd3, 0x40,
0x10, 0xc5, 0x71, 0xe2, 0x24, 0x93, 0x08, 0xca, 0x0a, 0x21, 0x53, 0x0e, 0x18, 0x4b, 0x40, 0x24,
0x50, 0xaa, 0xc0, 0x91, 0x13, 0x34, 0xb4, 0x88, 0x0b, 0xd5, 0x16, 0xb8, 0x46, 0xae, 0x3d, 0x09,
0x46, 0x8e, 0xd7, 0xec, 0xae, 0xfb, 0x9f, 0x38, 0x72, 0xe7, 0x47, 0x70, 0xe4, 0x8f, 0x70, 0x47,
0xfb, 0xe1, 0xda, 0x06, 0x59, 0xca, 0xa1, 0xb4, 0xb7, 0xf5, 0x9b, 0x37, 0x1f, 0xef, 0xcd, 0x6e,
0x02, 0x20, 0x51, 0xc8, 0x79, 0xc1, 0x99, 0x64, 0x64, 0xa0, 0xce, 0x62, 0x7f, 0xb6, 0x61, 0x6c,
0x93, 0xe1, 0xc1, 0x3a, 0xe5, 0x28, 0x24, 0xe3, 0x78, 0x70, 0xbe, 0x38, 0x43, 0x19, 0x2d, 0x6a,
0xc4, 0x24, 0xec, 0x3f, 0xea, 0x64, 0xc6, 0x6c, 0xbb, 0x65, 0xb9, 0xa1, 0x85, 0x3f, 0x7a, 0xd0,
0xff, 0x80, 0x42, 0x92, 0x00, 0x26, 0x09, 0x8a, 0x98, 0xa7, 0x85, 0x4c, 0x59, 0xee, 0x3b, 0x81,
0x33, 0x1b, 0xd3, 0x26, 0x44, 0x42, 0x70, 0x37, 0x28, 0xfd, 0x5e, 0xe0, 0xcc, 0x26, 0xcf, 0x6f,
0xce, 0xf5, 0x40, 0xf3, 0x63, 0x94, 0x2a, 0xfd, 0xed, 0x0d, 0xaa, 0x82, 0xe4, 0x29, 0x78, 0x31,
0xc7, 0x48, 0xa2, 0xef, 0x6a, 0xda, 0x6d, 0x4b, 0x3b, 0xd4, 0xa0, 0x65, 0x5a, 0x8a, 0x2a, 0x28,
0x50, 0xfa, 0xfd, 0x56, 0xc1, 0xd3, 0xba, 0xa0, 0x30, 0x05, 0xcb, 0x22, 0x51, 0x05, 0x07, 0xad,
0x82, 0x1f, 0x35, 0x58, 0x15, 0x34, 0x14, 0xf2, 0x12, 0xa6, 0xe6, 0xb4, 0x2a, 0x22, 0xf9, 0x59,
0xf8, 0x9e, 0x4e, 0xb9, 0xdb, 0x4a, 0x39, 0x51, 0x11, 0x9b, 0x37, 0x29, 0x6b, 0x48, 0x75, 0x4a,
0x30, 0x43, 0x89, 0xfe, 0xb0, 0xd5, 0x69, 0xa9, 0xc1, 0xaa, 0x93, 0xa1, 0xbc, 0xf6, 0xa0, 0xaf,
0xa2, 0xa1, 0x80, 0xa1, 0x75, 0x80, 0x04, 0x30, 0x4d, 0x58, 0xbc, 0xe2, 0xb8, 0xd6, 0xdd, 0xad,
0x83, 0x90, 0xb0, 0x98, 0xe2, 0x5a, 0xb5, 0x20, 0x47, 0x30, 0xe4, 0xf8, 0xb5, 0x44, 0x51, 0x99,
0xf8, 0x6c, 0x6e, 0x96, 0x34, 0xaf, 0x97, 0x67, 0x97, 0xa4, 0x7c, 0x5d, 0xb2, 0xb8, 0xdc, 0x62,
0x2e, 0xa9, 0xc9, 0xa1, 0x55, 0x72, 0xf8, 0xcd, 0x01, 0xa8, 0x0d, 0xdd, 0xa1, 0xf1, 0x7d, 0x18,
0x7f, 0x11, 0x2c, 0x5f, 0x25, 0x91, 0x8c, 0x74, 0xeb, 0x31, 0x1d, 0x29, 0x60, 0x19, 0xc9, 0x88,
0xbc, 0xaa, 0xa7, 0x32, 0x3b, 0x7b, 0xd2, 0x3d, 0xd5, 0x21, 0xdb, 0x6e, 0xd3, 0x7f, 0x06, 0x22,
0xf7, 0x60, 0x94, 0x8a, 0x15, 0x72, 0xce, 0xb8, 0xde, 0xe6, 0x88, 0x0e, 0x53, 0xf1, 0x46, 0x7d,
0x86, 0x3f, 0x1d, 0x18, 0x9e, 0xee, 0xec, 0xd0, 0x0c, 0x3c, 0x66, 0xee, 0x9f, 0x31, 0x68, 0xaf,
0xbe, 0x14, 0xef, 0x35, 0x4e, 0x6d, 0xbc, 0x2d, 0xc9, 0xed, 0x96, 0xd4, 0xbf, 0x04, 0x49, 0x83,
0xb6, 0xa4, 0xdf, 0x0e, 0x40, 0x7d, 0xfd, 0x76, 0x50, 0xf5, 0x0e, 0xa6, 0x05, 0xc7, 0x98, 0xe5,
0x49, 0xda, 0xd0, 0xf6, 0xb8, 0x7b, 0xa6, 0x93, 0x06, 0x9b, 0xb6, 0x72, 0xaf, 0x53, 0xf7, 0xf7,
0x1e, 0xdc, 0xfa, 0xeb, 0x0d, 0x5d, 0xb1, 0xf8, 0x05, 0x4c, 0xd6, 0x29, 0x66, 0x89, 0x7d, 0xde,
0x6e, 0xe0, 0x36, 0xee, 0xc8, 0x91, 0x8a, 0xa8, 0x96, 0x14, 0xd6, 0xd5, 0x51, 0x90, 0x07, 0x30,
0xd1, 0x7e, 0x9d, 0x47, 0x59, 0x89, 0xc2, 0xef, 0x07, 0xae, 0x9a, 0x4f, 0x41, 0x9f, 0x34, 0xd2,
0xf4, 0x6c, 0x70, 0x09, 0x9e, 0x79, 0x6d, 0xcf, 0x7e, 0x39, 0x00, 0xf5, 0x0f, 0xc8, 0x15, 0xdb,
0xf5, 0x7f, 0x5f, 0xf6, 0x31, 0x8c, 0x2f, 0x9e, 0x25, 0xd9, 0x03, 0x37, 0xca, 0x32, 0xad, 0x67,
0x44, 0xd5, 0x51, 0x3d, 0x65, 0xbd, 0x06, 0xe1, 0xf7, 0x3a, 0xd6, 0x64, 0xe3, 0xe1, 0x43, 0x18,
0x5f, 0x80, 0xe4, 0x0e, 0x0c, 0x34, 0xec, 0x3b, 0x7a, 0x53, 0xe6, 0xe3, 0xcc, 0xd3, 0x7f, 0x56,
0x2f, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0x6c, 0x8e, 0x38, 0xdd, 0x12, 0x07, 0x00, 0x00,
// 994 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xc4, 0x56, 0x5f, 0x6f, 0xdc, 0x44,
0x10, 0xaf, 0x7d, 0x67, 0x9f, 0x3d, 0x0e, 0xa5, 0xac, 0x50, 0x65, 0x0a, 0x88, 0xab, 0x15, 0x92,
0x83, 0xa2, 0x2b, 0x09, 0xe2, 0x09, 0x09, 0x94, 0x4b, 0x48, 0x2a, 0xa4, 0xaa, 0xc1, 0x57, 0xe0,
0x05, 0xe9, 0x70, 0xec, 0x71, 0x62, 0xe4, 0xf3, 0x5e, 0x77, 0xd7, 0x45, 0xfd, 0x48, 0x20, 0xf1,
0xc0, 0x37, 0xe1, 0x91, 0x4f, 0xc0, 0x37, 0x80, 0x67, 0xb4, 0x7f, 0x7c, 0xb6, 0xd3, 0x5c, 0xc9,
0x43, 0x29, 0x6f, 0xbb, 0x33, 0xbf, 0x99, 0x9d, 0xf9, 0xcd, 0xec, 0xec, 0x02, 0x08, 0xe4, 0x62,
0xba, 0x62, 0x54, 0x50, 0xe2, 0xc8, 0x35, 0xbf, 0x33, 0x39, 0xa7, 0xf4, 0xbc, 0xc4, 0xfb, 0x79,
0xc1, 0x90, 0x0b, 0xca, 0xf0, 0xfe, 0xd3, 0xbd, 0x33, 0x14, 0xc9, 0x5e, 0x2b, 0xd1, 0x06, 0x77,
0xde, 0xdf, 0x88, 0x4c, 0xe9, 0x72, 0x49, 0x2b, 0x03, 0xdb, 0xde, 0x08, 0x7b, 0x52, 0x23, 0x7b,
0xa6, 0x51, 0xd1, 0x14, 0xfc, 0xc7, 0xc8, 0xc5, 0xbc, 0x2e, 0x04, 0x92, 0xbb, 0xa0, 0x83, 0x09,
0xad, 0xf1, 0x60, 0x12, 0xec, 0x07, 0x53, 0xb5, 0x9b, 0x4a, 0x40, 0xac, 0x35, 0xd1, 0x9f, 0x36,
0x0c, 0xe5, 0x9e, 0x8c, 0x21, 0xc8, 0x90, 0xa7, 0xac, 0x58, 0x89, 0x82, 0x56, 0xa1, 0x35, 0xb6,
0x26, 0x7e, 0xdc, 0x15, 0x91, 0x08, 0x06, 0xe7, 0x28, 0x42, 0x7b, 0x6c, 0x4d, 0x82, 0xfd, 0x9b,
0xc6, 0xd7, 0x09, 0x0a, 0x69, 0xfe, 0xe0, 0x46, 0x2c, 0x95, 0xe4, 0x1e, 0xb8, 0x29, 0xc3, 0x44,
0x60, 0x38, 0x50, 0xb0, 0x37, 0x0c, 0xec, 0x50, 0x09, 0x0d, 0xd2, 0x40, 0xa4, 0x43, 0x8e, 0x22,
0x1c, 0xf6, 0x1c, 0xce, 0x5b, 0x87, 0x5c, 0x3b, 0xac, 0x57, 0x99, 0x74, 0xe8, 0xf4, 0x1c, 0x7e,
0xa3, 0x84, 0x8d, 0x43, 0x0d, 0x21, 0x9f, 0xc1, 0x96, 0x5e, 0x2d, 0x56, 0x89, 0xb8, 0xe0, 0xa1,
0xab, 0x4c, 0x6e, 0xf7, 0x4c, 0x4e, 0xa5, 0xc6, 0xd8, 0x05, 0x75, 0x2b, 0x92, 0x27, 0x65, 0x58,
0xa2, 0xc0, 0x70, 0xd4, 0x3b, 0xe9, 0x48, 0x09, 0x9b, 0x93, 0x34, 0x84, 0x4c, 0xc0, 0x51, 0xac,
0x87, 0x9e, 0xc2, 0xde, 0x32, 0xd8, 0xaf, 0xa5, 0xcc, 0x40, 0x35, 0x60, 0xe6, 0xc2, 0x50, 0xea,
0x22, 0x0e, 0x23, 0xc3, 0x15, 0x19, 0xc3, 0x56, 0x46, 0xd3, 0x05, 0xc3, 0x5c, 0xc5, 0x69, 0xb8,
0x86, 0x8c, 0xa6, 0x31, 0xe6, 0x32, 0x18, 0x72, 0x0c, 0x23, 0x86, 0x4f, 0x6a, 0xe4, 0x0d, 0xdd,
0x1f, 0x4d, 0x75, 0xf5, 0xa7, 0x6d, 0xf3, 0x98, 0xea, 0xcb, 0x0a, 0x1c, 0xd1, 0xb4, 0x5e, 0x62,
0x25, 0x62, 0x6d, 0x13, 0x37, 0xc6, 0xd1, 0xcf, 0x16, 0x40, 0x4b, 0xfd, 0x35, 0x0e, 0x7e, 0x1b,
0xfc, 0x1f, 0x39, 0xad, 0x16, 0x59, 0x22, 0x12, 0x75, 0xb4, 0x1f, 0x7b, 0x52, 0x70, 0x94, 0x88,
0x84, 0x1c, 0xb4, 0x51, 0xe9, 0xea, 0xee, 0x6e, 0x8e, 0xea, 0x90, 0x2e, 0x97, 0xc5, 0x73, 0x01,
0x91, 0xb7, 0xc0, 0x2b, 0xf8, 0x02, 0x19, 0xa3, 0x4c, 0xd5, 0xdd, 0x8b, 0x47, 0x05, 0xff, 0x52,
0x6e, 0xa3, 0xdf, 0x2d, 0x18, 0xcd, 0xaf, 0xcd, 0xd0, 0x04, 0x5c, 0xaa, 0x3b, 0xd5, 0xee, 0x55,
0x60, 0x8e, 0xe2, 0x91, 0x92, 0xc7, 0x46, 0xdf, 0x4f, 0x69, 0xb0, 0x39, 0xa5, 0xe1, 0x4b, 0x48,
0xc9, 0xe9, 0xa7, 0xf4, 0x97, 0x05, 0xd0, 0x36, 0xea, 0x35, 0xb2, 0xfa, 0x0a, 0xb6, 0x56, 0x0c,
0x53, 0x5a, 0x65, 0x45, 0x27, 0xb7, 0x9d, 0xcd, 0x31, 0x9d, 0x76, 0xd0, 0x71, 0xcf, 0xf6, 0xff,
0xcc, 0xfb, 0x37, 0x1b, 0x5e, 0xbf, 0x74, 0xdb, 0x5e, 0x71, 0xf2, 0x7b, 0x10, 0xe4, 0x05, 0x96,
0x99, 0x19, 0x04, 0x03, 0x35, 0xff, 0x9a, 0x1e, 0x39, 0x96, 0x1a, 0x79, 0x64, 0x0c, 0x79, 0xb3,
0xe4, 0xe4, 0x3d, 0x08, 0x14, 0x5f, 0x4f, 0x93, 0xb2, 0x46, 0x1e, 0x0e, 0xc7, 0x03, 0x19, 0x9f,
0x14, 0x7d, 0xab, 0x24, 0x5d, 0xce, 0x9c, 0x97, 0xc0, 0x99, 0xdb, 0xe7, 0xec, 0x0f, 0x0b, 0xa0,
0x1d, 0x35, 0xaf, 0x98, 0xae, 0xff, 0xf6, 0x66, 0x9f, 0x80, 0xbf, 0xbe, 0x96, 0xe4, 0x16, 0x0c,
0x92, 0xb2, 0x54, 0xf9, 0x78, 0xb1, 0x5c, 0xca, 0xab, 0xac, 0xca, 0xc0, 0x43, 0x7b, 0x43, 0x99,
0x8c, 0x3e, 0xfa, 0xd5, 0x02, 0x7f, 0x3d, 0x62, 0x65, 0x83, 0xa7, 0xb4, 0x2c, 0xbb, 0xfc, 0x78,
0x52, 0xa0, 0xd8, 0xd9, 0x85, 0x51, 0x5a, 0x26, 0x35, 0xc7, 0xc6, 0xeb, 0x6b, 0xcd, 0x4b, 0xa4,
0xa4, 0x71, 0xa3, 0x25, 0x5f, 0x34, 0x93, 0x5c, 0x27, 0xfe, 0xc1, 0xe6, 0xc4, 0xe7, 0x82, 0xd5,
0xa9, 0xa8, 0x19, 0x66, 0x2a, 0x06, 0x33, 0xe0, 0x5f, 0x94, 0xf8, 0xdf, 0x36, 0xb8, 0xfa, 0x3c,
0xb2, 0x0b, 0x2e, 0xc7, 0x12, 0x53, 0xa1, 0x22, 0x6d, 0xc3, 0x99, 0x2b, 0xa1, 0x7c, 0x59, 0xb4,
0x9a, 0x6c, 0x83, 0xf3, 0xd3, 0x05, 0x32, 0x34, 0xf5, 0xdc, 0x32, 0xb8, 0xef, 0xa4, 0x4c, 0xbe,
0x2a, 0x4a, 0x49, 0xee, 0x81, 0x47, 0x59, 0x86, 0x6c, 0x71, 0xd6, 0x04, 0xde, 0xbc, 0x9f, 0x8f,
0xa4, 0x78, 0xf6, 0xec, 0xc1, 0x8d, 0x78, 0x44, 0xf5, 0x92, 0x84, 0xe0, 0xd2, 0x3c, 0x6f, 0x9e,
0x5a, 0x47, 0x1e, 0xa6, 0xf7, 0xe4, 0x36, 0x38, 0x65, 0xb1, 0x2c, 0x74, 0x43, 0x4b, 0x85, 0xde,
0x92, 0x0f, 0xc1, 0xe3, 0x22, 0x61, 0x62, 0x91, 0x08, 0xf3, 0x88, 0xae, 0xe9, 0xab, 0x19, 0xa7,
0x4c, 0x7a, 0x57, 0x80, 0x03, 0x41, 0x3e, 0x86, 0xc0, 0x60, 0x73, 0x81, 0xcc, 0x3c, 0x9e, 0xcf,
0xc1, 0x41, 0xc3, 0x25, 0x84, 0xec, 0x80, 0x8b, 0x55, 0x26, 0x7d, 0x7b, 0x57, 0x83, 0x1d, 0xac,
0xb2, 0x03, 0x41, 0xa6, 0x00, 0x12, 0x77, 0x86, 0x39, 0x65, 0x18, 0xfa, 0x57, 0x63, 0x7d, 0xac,
0xb2, 0x99, 0x42, 0xcc, 0x3c, 0x70, 0x75, 0x55, 0xa3, 0x7d, 0x70, 0x35, 0xb1, 0x9d, 0xe6, 0xb2,
0xfe, 0xa5, 0xb9, 0xbe, 0x07, 0x47, 0x91, 0x4c, 0xb6, 0x61, 0xb8, 0x6e, 0xa9, 0xab, 0x0c, 0x94,
0x96, 0xdc, 0x04, 0x9b, 0xae, 0xcc, 0x13, 0x69, 0xd3, 0x15, 0x79, 0x17, 0xa0, 0x1d, 0x1f, 0x66,
0xde, 0xfa, 0xeb, 0xe9, 0x11, 0x3d, 0x84, 0x91, 0xa9, 0xcc, 0x35, 0xfd, 0xbf, 0x03, 0x7e, 0x56,
0x30, 0x4c, 0xd7, 0x77, 0xdb, 0x8f, 0x5b, 0x41, 0xf4, 0x03, 0xb8, 0x9a, 0x01, 0xf2, 0xa9, 0x1e,
0x14, 0xbc, 0x4a, 0x56, 0xfc, 0x82, 0x36, 0xed, 0x45, 0x9a, 0xcf, 0x0b, 0x4d, 0xe7, 0x46, 0x13,
0x07, 0x59, 0xbb, 0xb9, 0x3c, 0xed, 0xec, 0xcb, 0xd3, 0x2e, 0xfa, 0x1c, 0x82, 0x8e, 0x31, 0x21,
0x9d, 0xa0, 0x7d, 0x13, 0xe2, 0x8b, 0x3e, 0x0b, 0xd1, 0x5d, 0xf0, 0xd7, 0x29, 0x91, 0x37, 0xc1,
0x51, 0x2c, 0xab, 0x22, 0xf8, 0xb1, 0xde, 0xcc, 0x1e, 0xc2, 0x4e, 0x4a, 0x97, 0xcd, 0x85, 0x4b,
0x4b, 0x5a, 0x67, 0x9d, 0x6b, 0x97, 0xd2, 0x2a, 0xa7, 0x6c, 0x99, 0x54, 0x29, 0xfe, 0x62, 0x47,
0x27, 0x1a, 0x74, 0xa8, 0x40, 0xc7, 0x6b, 0xd0, 0x63, 0x95, 0xe5, 0xa9, 0xfc, 0xfa, 0x9e, 0xb9,
0xea, 0x07, 0xfc, 0xc9, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x8d, 0x96, 0x46, 0xb3, 0x8d, 0x0b,
0x00, 0x00,
}

View File

@ -19,6 +19,7 @@ import (
"flag"
"fmt"
"log"
"math"
"os"
"sort"
"testing"
@ -259,6 +260,7 @@ func TestIntegration_GetAll(t *testing.T) {
for i := 0; i < 5; i++ {
doc := coll.NewDoc()
docRefs = append(docRefs, doc)
// TODO(jba): omit one create so we can test missing doc behavior.
mustCreate("GetAll #1", t, doc, getAll{N: i})
}
docSnapshots, err := iClient.GetAll(ctx, docRefs)
@ -277,6 +279,9 @@ func TestIntegration_GetAll(t *testing.T) {
if got != want {
t.Errorf("%d: got %+v, want %+v", i, got, want)
}
if ds.ReadTime.IsZero() {
t.Errorf("%d: got zero read time", i)
}
}
}
@ -686,6 +691,38 @@ func TestIntegration_Query(t *testing.T) {
}
}
// Test unary filters.
func TestIntegration_QueryUnary(t *testing.T) {
ctx := context.Background()
coll := integrationColl(t)
mustCreate("q", t, coll.NewDoc(), map[string]interface{}{"x": 2, "q": "a"})
mustCreate("q", t, coll.NewDoc(), map[string]interface{}{"x": 2, "q": nil})
mustCreate("q", t, coll.NewDoc(), map[string]interface{}{"x": 2, "q": math.NaN()})
wantNull := map[string]interface{}{"q": nil}
wantNaN := map[string]interface{}{"q": math.NaN()}
base := coll.Select("q").Where("x", "==", 2)
for _, test := range []struct {
q Query
want map[string]interface{}
}{
{base.Where("q", "==", nil), wantNull},
{base.Where("q", "==", math.NaN()), wantNaN},
} {
got, err := test.q.Documents(ctx).GetAll()
if err != nil {
t.Fatal(err)
}
if len(got) != 1 {
t.Errorf("got %d responses, want 1", len(got))
continue
}
if g, w := got[0].Data(), test.want; !testEqual(g, w) {
t.Errorf("%v: got %v, want %v", test.q, g, w)
}
}
}
// Test the special DocumentID field in queries.
func TestIntegration_QueryName(t *testing.T) {
ctx := context.Background()
@ -783,6 +820,7 @@ func TestIntegration_RunTransaction(t *testing.T) {
}
return anError
}
mustCreate("RunTransaction", t, patDoc, pat)
err := client.RunTransaction(ctx, incPat)
if err != nil {
@ -813,6 +851,41 @@ func TestIntegration_RunTransaction(t *testing.T) {
}
}
func TestIntegration_TransactionGetAll(t *testing.T) {
ctx := context.Background()
type Player struct {
Name string
Score int
}
lee := Player{Name: "Lee", Score: 3}
sam := Player{Name: "Sam", Score: 1}
client := integrationClient(t)
leeDoc := iColl.Doc("lee")
samDoc := iColl.Doc("sam")
mustCreate("TransactionGetAll", t, leeDoc, lee)
mustCreate("TransactionGetAll", t, samDoc, sam)
err := client.RunTransaction(ctx, func(_ context.Context, tx *Transaction) error {
docs, err := tx.GetAll([]*DocumentRef{samDoc, leeDoc})
if err != nil {
return err
}
for i, want := range []Player{sam, lee} {
var got Player
if err := docs[i].DataTo(&got); err != nil {
return err
}
if !testutil.Equal(got, want) {
return fmt.Errorf("got %+v, want %+v", got, want)
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
}
func codeEq(t *testing.T, msg string, code codes.Code, err error) {
if grpc.Code(err) != code {
t.Fatalf("%s:\ngot <%v>\nwant code %s", msg, err, code)

View File

@ -21,6 +21,9 @@ database.
See https://cloud.google.com/firestore/docs for an introduction
to Cloud Firestore and additional help on using the Firestore API.
Note: you can't use both Cloud Firestore and Cloud Datastore in the same
project.
Creating a Client
To start working with this package, create a client with a project ID:

View File

@ -18,9 +18,12 @@ package firestore
import (
"fmt"
"strings"
"cloud.google.com/go/internal/testutil"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/empty"
@ -54,7 +57,10 @@ func newMockServer() (*mockServer, error) {
// addRPC adds a (request, response) pair to the server's list of expected
// interactions. The server will compare the incoming request with wantReq
// using proto.Equal.
// using proto.Equal. The response can be a message or an error.
//
// For the Listen RPC, resp should be a []interface{}, where each element
// is either ListenResponse or an error.
//
// Passing nil for wantReq disables the request check.
func (s *mockServer) addRPC(wantReq proto.Message, resp interface{}) {
@ -174,3 +180,28 @@ func (s *mockServer) Rollback(_ context.Context, req *pb.RollbackRequest) (*empt
}
return res.(*empty.Empty), nil
}
func (s *mockServer) Listen(stream pb.Firestore_ListenServer) error {
req, err := stream.Recv()
if err != nil {
return err
}
responses, err := s.popRPC(req)
if err != nil {
if status.Code(err) == codes.Unknown && strings.Contains(err.Error(), "mockServer") {
// The stream will retry on Unknown, but we don't want that to happen if
// the error comes from us.
panic(err)
}
return err
}
for _, res := range responses.([]interface{}) {
if err, ok := res.(error); ok {
return err
}
if err := stream.Send(res.(*pb.ListenResponse)); err != nil {
return err
}
}
return nil
}

216
vendor/cloud.google.com/go/firestore/order.go generated vendored Normal file
View File

@ -0,0 +1,216 @@
// Copyright 2018 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 firestore
import (
"bytes"
"fmt"
"math"
"sort"
"strings"
tspb "github.com/golang/protobuf/ptypes/timestamp"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
)
// Returns a negative number, zero, or a positive number depending on whether a is
// less than, equal to, or greater than b according to Firestore's ordering of
// values.
func compareValues(a, b *pb.Value) int {
ta := typeOrder(a)
tb := typeOrder(b)
if ta != tb {
return compareInt64s(int64(ta), int64(tb))
}
switch a := a.ValueType.(type) {
case *pb.Value_NullValue:
return 0 // nulls are equal
case *pb.Value_BooleanValue:
av := a.BooleanValue
bv := b.GetBooleanValue()
switch {
case av && !bv:
return 1
case bv && !av:
return -1
default:
return 0
}
case *pb.Value_IntegerValue:
return compareNumbers(float64(a.IntegerValue), toFloat(b))
case *pb.Value_DoubleValue:
return compareNumbers(a.DoubleValue, toFloat(b))
case *pb.Value_TimestampValue:
return compareTimestamps(a.TimestampValue, b.GetTimestampValue())
case *pb.Value_StringValue:
return strings.Compare(a.StringValue, b.GetStringValue())
case *pb.Value_BytesValue:
return bytes.Compare(a.BytesValue, b.GetBytesValue())
case *pb.Value_ReferenceValue:
return compareReferences(a.ReferenceValue, b.GetReferenceValue())
case *pb.Value_GeoPointValue:
ag := a.GeoPointValue
bg := b.GetGeoPointValue()
if ag.Latitude != bg.Latitude {
return compareFloat64s(ag.Latitude, bg.Latitude)
}
return compareFloat64s(ag.Longitude, bg.Longitude)
case *pb.Value_ArrayValue:
return compareArrays(a.ArrayValue.Values, b.GetArrayValue().Values)
case *pb.Value_MapValue:
return compareMaps(a.MapValue.Fields, b.GetMapValue().Fields)
default:
panic(fmt.Sprintf("bad value type: %v", a))
}
}
// Treats NaN as less than any non-NaN.
func compareNumbers(a, b float64) int {
switch {
case math.IsNaN(a):
if math.IsNaN(b) {
return 0
}
return -1
case math.IsNaN(b):
return 1
default:
return compareFloat64s(a, b)
}
}
// Return v as a float64, assuming it's an Integer or Double.
func toFloat(v *pb.Value) float64 {
if x, ok := v.ValueType.(*pb.Value_IntegerValue); ok {
return float64(x.IntegerValue)
}
return v.GetDoubleValue()
}
func compareTimestamps(a, b *tspb.Timestamp) int {
if c := compareInt64s(a.Seconds, b.Seconds); c != 0 {
return c
}
return compareInt64s(int64(a.Nanos), int64(b.Nanos))
}
func compareReferences(a, b string) int {
// Compare path components lexicographically.
pa := strings.Split(a, "/")
pb := strings.Split(b, "/")
for i := 0; i < len(pa) && i < len(pb); i++ {
if c := strings.Compare(pa[i], pb[i]); c != 0 {
return c
}
}
return compareInt64s(int64(len(pa)), int64(len(pb)))
}
func compareArrays(a, b []*pb.Value) int {
for i := 0; i < len(a) && i < len(b); i++ {
if c := compareValues(a[i], b[i]); c != 0 {
return c
}
}
return compareInt64s(int64(len(a)), int64(len(b)))
}
func compareMaps(a, b map[string]*pb.Value) int {
sortedKeys := func(m map[string]*pb.Value) []string {
var ks []string
for k := range m {
ks = append(ks, k)
}
sort.Strings(ks)
return ks
}
aks := sortedKeys(a)
bks := sortedKeys(b)
for i := 0; i < len(aks) && i < len(bks); i++ {
if c := strings.Compare(aks[i], bks[i]); c != 0 {
return c
}
k := aks[i]
if c := compareValues(a[k], b[k]); c != 0 {
return c
}
}
return compareInt64s(int64(len(aks)), int64(len(bks)))
}
func compareFloat64s(a, b float64) int {
switch {
case a < b:
return -1
case a > b:
return 1
default:
return 0
}
}
func compareInt64s(a, b int64) int {
switch {
case a < b:
return -1
case a > b:
return 1
default:
return 0
}
}
// Return an integer corresponding to the type of value stored in v, such that
// comparing the resulting integers gives the Firestore ordering for types.
func typeOrder(v *pb.Value) int {
switch v.ValueType.(type) {
case *pb.Value_NullValue:
return 0
case *pb.Value_BooleanValue:
return 1
case *pb.Value_IntegerValue:
return 2
case *pb.Value_DoubleValue:
return 2
case *pb.Value_TimestampValue:
return 3
case *pb.Value_StringValue:
return 4
case *pb.Value_BytesValue:
return 5
case *pb.Value_ReferenceValue:
return 6
case *pb.Value_GeoPointValue:
return 7
case *pb.Value_ArrayValue:
return 8
case *pb.Value_MapValue:
return 9
default:
panic(fmt.Sprintf("bad value type: %v", v))
}
}

118
vendor/cloud.google.com/go/firestore/order_test.go generated vendored Normal file
View File

@ -0,0 +1,118 @@
// Copyright 2018 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 firestore
import (
"math"
"testing"
"time"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"google.golang.org/genproto/googleapis/type/latlng"
)
func TestCompareValues(t *testing.T) {
// Ordered list of values.
vals := []*pb.Value{
nullValue,
boolval(false),
boolval(true),
floatval(math.NaN()),
floatval(math.Inf(-1)),
floatval(-math.MaxFloat64),
int64val(math.MinInt64),
floatval(-1.1),
intval(-1),
intval(0),
floatval(math.SmallestNonzeroFloat64),
intval(1),
floatval(1.1),
intval(2),
int64val(math.MaxInt64),
floatval(math.MaxFloat64),
floatval(math.Inf(1)),
tsval(time.Date(2016, 5, 20, 10, 20, 0, 0, time.UTC)),
tsval(time.Date(2016, 10, 21, 15, 32, 0, 0, time.UTC)),
strval(""),
strval("\u0000\ud7ff\ue000\uffff"),
strval("(╯°□°)╯︵ ┻━┻"),
strval("a"),
strval("abc def"),
strval("e\u0301b"),
strval("æ"),
strval("\u00e9a"),
bytesval([]byte{}),
bytesval([]byte{0}),
bytesval([]byte{0, 1, 2, 3, 4}),
bytesval([]byte{0, 1, 2, 4, 3}),
bytesval([]byte{255}),
refval("projects/p1/databases/d1/documents/c1/doc1"),
refval("projects/p1/databases/d1/documents/c1/doc2"),
refval("projects/p1/databases/d1/documents/c1/doc2/c2/doc1"),
refval("projects/p1/databases/d1/documents/c1/doc2/c2/doc2"),
refval("projects/p1/databases/d1/documents/c10/doc1"),
refval("projects/p1/databases/dkkkkklkjnjkkk1/documents/c2/doc1"),
refval("projects/p2/databases/d2/documents/c1/doc1"),
refval("projects/p2/databases/d2/documents/c1-/doc1"),
geopoint(-90, -180),
geopoint(-90, 0),
geopoint(-90, 180),
geopoint(0, -180),
geopoint(0, 0),
geopoint(0, 180),
geopoint(1, -180),
geopoint(1, 0),
geopoint(1, 180),
geopoint(90, -180),
geopoint(90, 0),
geopoint(90, 180),
arrayval(),
arrayval(strval("bar")),
arrayval(strval("foo")),
arrayval(strval("foo"), intval(1)),
arrayval(strval("foo"), intval(2)),
arrayval(strval("foo"), strval("0")),
mapval(map[string]*pb.Value{"bar": intval(0)}),
mapval(map[string]*pb.Value{"bar": intval(0), "foo": intval(1)}),
mapval(map[string]*pb.Value{"foo": intval(1)}),
mapval(map[string]*pb.Value{"foo": intval(2)}),
mapval(map[string]*pb.Value{"foo": strval("0")}),
}
for i, v1 := range vals {
if got := compareValues(v1, v1); got != 0 {
t.Errorf("compare(%v, %v) == %d, want 0", v1, v1, got)
}
for _, v2 := range vals[i+1:] {
if got := compareValues(v1, v2); got != -1 {
t.Errorf("compare(%v, %v) == %d, want -1", v1, v2, got)
}
if got := compareValues(v2, v1); got != 1 {
t.Errorf("compare(%v, %v) == %d, want 1", v1, v2, got)
}
}
}
// Integers and Doubles order the same.
n1 := intval(17)
n2 := floatval(17)
if got := compareValues(n1, n2); got != 0 {
t.Errorf("compare(%v, %v) == %d, want 0", n1, n2, got)
}
}
func geopoint(lat, lng float64) *pb.Value {
return geoval(&latlng.LatLng{Latitude: lat, Longitude: lng})
}

View File

@ -20,6 +20,7 @@ import (
"io"
"math"
"reflect"
"strings"
"golang.org/x/net/context"
@ -43,10 +44,15 @@ type Query struct {
offset int32
limit *wrappers.Int32Value
startVals, endVals []interface{}
startDoc, endDoc *DocumentSnapshot
startBefore, endBefore bool
err error
}
func (q *Query) collectionPath() string {
return q.parentPath + "/documents/" + q.collectionID
}
// DocumentID is the special field name representing the ID of a document
// in queries.
const DocumentID = "__name__"
@ -65,18 +71,17 @@ func (q Query) Select(paths ...string) Query {
}
fps = append(fps, fp)
}
if fps == nil {
q.selection = []FieldPath{{DocumentID}}
} else {
q.selection = fps
}
return q
return q.SelectPaths(fps...)
}
// SelectPaths returns a new Query that specifies the field paths
// to return from the result documents.
func (q Query) SelectPaths(fieldPaths ...FieldPath) Query {
q.selection = fieldPaths
if len(fieldPaths) == 0 {
q.selection = []FieldPath{{DocumentID}}
} else {
q.selection = fieldPaths
}
return q
}
@ -128,7 +133,7 @@ func (q Query) OrderBy(path string, dir Direction) Query {
q.err = err
return q
}
q.orders = append(append([]order(nil), q.orders...), order{fp, dir})
q.orders = append(q.copyOrders(), order{fp, dir})
return q
}
@ -136,10 +141,14 @@ func (q Query) OrderBy(path string, dir Direction) Query {
// returned. A Query can have multiple OrderBy/OrderByPath specifications.
// OrderByPath appends the specification to the list of existing ones.
func (q Query) OrderByPath(fp FieldPath, dir Direction) Query {
q.orders = append(append([]order(nil), q.orders...), order{fp, dir})
q.orders = append(q.copyOrders(), order{fp, dir})
return q
}
func (q *Query) copyOrders() []order {
return append([]order(nil), q.orders...)
}
// Offset returns a new Query that specifies the number of initial results to skip.
// It must not be negative.
func (q Query) Offset(n int) Query {
@ -155,8 +164,13 @@ func (q Query) Limit(n int) Query {
}
// StartAt returns a new Query that specifies that results should start at
// the document with the given field values. The field path corresponding to
// each value is taken from the corresponding OrderBy call. For example, in
// the document with the given field values.
//
// If StartAt is called with a single DocumentSnapshot, its field values are used.
// The DocumentSnapshot must have all the fields mentioned in the OrderBy clauses.
//
// Otherwise, StartAt should be called with one field value for each OrderBy clause,
// in the order that they appear. For example, in
// q.OrderBy("X", Asc).OrderBy("Y", Desc).StartAt(1, 2)
// results will begin at the first document where X = 1 and Y = 2.
//
@ -167,8 +181,9 @@ func (q Query) Limit(n int) Query {
// client.Collection("States").OrderBy(DocumentID, firestore.Asc).StartAt("NewYork")
//
// Calling StartAt overrides a previous call to StartAt or StartAfter.
func (q Query) StartAt(fieldValues ...interface{}) Query {
q.startVals, q.startBefore = fieldValues, true
func (q Query) StartAt(docSnapshotOrFieldValues ...interface{}) Query {
q.startBefore = true
q.startVals, q.startDoc, q.err = q.processCursorArg("StartAt", docSnapshotOrFieldValues)
return q
}
@ -176,8 +191,9 @@ func (q Query) StartAt(fieldValues ...interface{}) Query {
// the document with the given field values. See Query.StartAt for more information.
//
// Calling StartAfter overrides a previous call to StartAt or StartAfter.
func (q Query) StartAfter(fieldValues ...interface{}) Query {
q.startVals, q.startBefore = fieldValues, false
func (q Query) StartAfter(docSnapshotOrFieldValues ...interface{}) Query {
q.startBefore = false
q.startVals, q.startDoc, q.err = q.processCursorArg("StartAfter", docSnapshotOrFieldValues)
return q
}
@ -185,8 +201,9 @@ func (q Query) StartAfter(fieldValues ...interface{}) Query {
// document with the given field values. See Query.StartAt for more information.
//
// Calling EndAt overrides a previous call to EndAt or EndBefore.
func (q Query) EndAt(fieldValues ...interface{}) Query {
q.endVals, q.endBefore = fieldValues, false
func (q Query) EndAt(docSnapshotOrFieldValues ...interface{}) Query {
q.endBefore = false
q.endVals, q.endDoc, q.err = q.processCursorArg("EndAt", docSnapshotOrFieldValues)
return q
}
@ -194,11 +211,24 @@ func (q Query) EndAt(fieldValues ...interface{}) Query {
// the document with the given field values. See Query.StartAt for more information.
//
// Calling EndBefore overrides a previous call to EndAt or EndBefore.
func (q Query) EndBefore(fieldValues ...interface{}) Query {
q.endVals, q.endBefore = fieldValues, true
func (q Query) EndBefore(docSnapshotOrFieldValues ...interface{}) Query {
q.endBefore = true
q.endVals, q.endDoc, q.err = q.processCursorArg("EndBefore", docSnapshotOrFieldValues)
return q
}
func (q *Query) processCursorArg(name string, docSnapshotOrFieldValues []interface{}) ([]interface{}, *DocumentSnapshot, error) {
for _, e := range docSnapshotOrFieldValues {
if ds, ok := e.(*DocumentSnapshot); ok {
if len(docSnapshotOrFieldValues) == 1 {
return nil, ds, nil
}
return nil, nil, fmt.Errorf("firestore: a document snapshot must be the only argument to %s", name)
}
}
return docSnapshotOrFieldValues, nil, nil
}
func (q Query) query() *Query { return &q }
func (q Query) toProto() (*pb.StructuredQuery, error) {
@ -245,33 +275,79 @@ func (q Query) toProto() (*pb.StructuredQuery, error) {
cf.Filters = append(cf.Filters, pf)
}
}
for _, ord := range q.orders {
orders := q.orders
if q.startDoc != nil || q.endDoc != nil {
orders = q.adjustOrders()
}
for _, ord := range orders {
po, err := ord.toProto()
if err != nil {
return nil, err
}
p.OrderBy = append(p.OrderBy, po)
}
// StartAt and EndAt must have values that correspond exactly to the explicit order-by fields.
if len(q.startVals) != 0 {
vals, err := q.toPositionValues(q.startVals)
if err != nil {
return nil, err
}
p.StartAt = &pb.Cursor{Values: vals, Before: q.startBefore}
cursor, err := q.toCursor(q.startVals, q.startDoc, q.startBefore, orders)
if err != nil {
return nil, err
}
if len(q.endVals) != 0 {
vals, err := q.toPositionValues(q.endVals)
if err != nil {
return nil, err
}
p.EndAt = &pb.Cursor{Values: vals, Before: q.endBefore}
p.StartAt = cursor
cursor, err = q.toCursor(q.endVals, q.endDoc, q.endBefore, orders)
if err != nil {
return nil, err
}
p.EndAt = cursor
return p, nil
}
// If there is a start/end that uses a Document Snapshot, we may need to adjust the OrderBy
// clauses that the user provided: we add OrderBy(__name__) if it isn't already present, and
// we make sure we don't invalidate the original query by adding an OrderBy for inequality filters.
func (q *Query) adjustOrders() []order {
// If the user is already ordering by document ID, don't change anything.
for _, ord := range q.orders {
if ord.isDocumentID() {
return q.orders
}
}
// If there are OrderBy clauses, append an OrderBy(DocumentID), using the direction of the last OrderBy clause.
if len(q.orders) > 0 {
return append(q.copyOrders(), order{
fieldPath: FieldPath{DocumentID},
dir: q.orders[len(q.orders)-1].dir,
})
}
// If there are no OrderBy clauses but there is an inequality, add an OrderBy clause
// for the field of the first inequality.
var orders []order
for _, f := range q.filters {
if f.op != "==" {
orders = []order{{fieldPath: f.fieldPath, dir: Asc}}
break
}
}
// Add an ascending OrderBy(DocumentID).
return append(orders, order{fieldPath: FieldPath{DocumentID}, dir: Asc})
}
func (q *Query) toCursor(fieldValues []interface{}, ds *DocumentSnapshot, before bool, orders []order) (*pb.Cursor, error) {
var vals []*pb.Value
var err error
if ds != nil {
vals, err = q.docSnapshotToCursorValues(ds, orders)
} else if len(fieldValues) != 0 {
vals, err = q.fieldValuesToCursorValues(fieldValues)
} else {
return nil, nil
}
if err != nil {
return nil, err
}
return &pb.Cursor{Values: vals, Before: before}, nil
}
// toPositionValues converts the field values to protos.
func (q *Query) toPositionValues(fieldValues []interface{}) ([]*pb.Value, error) {
func (q *Query) fieldValuesToCursorValues(fieldValues []interface{}) ([]*pb.Value, error) {
if len(fieldValues) != len(q.orders) {
return nil, errors.New("firestore: number of field values in StartAt/StartAfter/EndAt/EndBefore does not match number of OrderBy fields")
}
@ -279,12 +355,14 @@ func (q *Query) toPositionValues(fieldValues []interface{}) ([]*pb.Value, error)
var err error
for i, ord := range q.orders {
fval := fieldValues[i]
if len(ord.fieldPath) == 1 && ord.fieldPath[0] == DocumentID {
if ord.isDocumentID() {
// TODO(jba): support DocumentRefs as well as strings.
// TODO(jba): error if document ref does not belong to the right collection.
docID, ok := fval.(string)
if !ok {
return nil, fmt.Errorf("firestore: expected doc ID for DocumentID field, got %T", fval)
}
vals[i] = &pb.Value{&pb.Value_ReferenceValue{q.parentPath + "/documents/" + q.collectionID + "/" + docID}}
vals[i] = &pb.Value{&pb.Value_ReferenceValue{q.collectionPath() + "/" + docID}}
} else {
var sawTransform bool
vals[i], sawTransform, err = toProtoValue(reflect.ValueOf(fval))
@ -299,6 +377,62 @@ func (q *Query) toPositionValues(fieldValues []interface{}) ([]*pb.Value, error)
return vals, nil
}
func (q *Query) docSnapshotToCursorValues(ds *DocumentSnapshot, orders []order) ([]*pb.Value, error) {
// TODO(jba): error if doc snap does not belong to the right collection.
vals := make([]*pb.Value, len(orders))
for i, ord := range orders {
if ord.isDocumentID() {
dp, qp := ds.Ref.Parent.Path, q.collectionPath()
if dp != qp {
return nil, fmt.Errorf("firestore: document snapshot for %s passed to query on %s", dp, qp)
}
vals[i] = &pb.Value{&pb.Value_ReferenceValue{ds.Ref.Path}}
} else {
val, err := valueAtPath(ord.fieldPath, ds.proto.Fields)
if err != nil {
return nil, err
}
vals[i] = val
}
}
return vals, nil
}
// Returns a function that compares DocumentSnapshots according to q's ordering.
func (q Query) compareFunc() func(d1, d2 *DocumentSnapshot) (int, error) {
// Add implicit sorting by name, using the last specified direction.
lastDir := Asc
if len(q.orders) > 0 {
lastDir = q.orders[len(q.orders)-1].dir
}
orders := append(q.copyOrders(), order{[]string{DocumentID}, lastDir})
return func(d1, d2 *DocumentSnapshot) (int, error) {
for _, ord := range orders {
var cmp int
if len(ord.fieldPath) == 1 && ord.fieldPath[0] == DocumentID {
cmp = strings.Compare(d1.Ref.Path, d2.Ref.Path)
} else {
v1, err := valueAtPath(ord.fieldPath, d1.proto.Fields)
if err != nil {
return 0, err
}
v2, err := valueAtPath(ord.fieldPath, d2.proto.Fields)
if err != nil {
return 0, err
}
cmp = compareValues(v1, v2)
}
if cmp != 0 {
if ord.dir == Desc {
cmp = -cmp
}
return cmp, nil
}
}
return 0, nil
}
}
type filter struct {
fieldPath FieldPath
op string
@ -309,6 +443,21 @@ func (f filter) toProto() (*pb.StructuredQuery_Filter, error) {
if err := f.fieldPath.validate(); err != nil {
return nil, err
}
if uop, ok := unaryOpFor(f.value); ok {
if f.op != "==" {
return nil, fmt.Errorf("firestore: must use '==' when comparing %v", f.value)
}
return &pb.StructuredQuery_Filter{
FilterType: &pb.StructuredQuery_Filter_UnaryFilter{
UnaryFilter: &pb.StructuredQuery_UnaryFilter{
OperandType: &pb.StructuredQuery_UnaryFilter_Field{
Field: fref(f.fieldPath),
},
Op: uop,
},
},
}, nil
}
var op pb.StructuredQuery_FieldFilter_Operator
switch f.op {
case "<":
@ -333,7 +482,7 @@ func (f filter) toProto() (*pb.StructuredQuery_Filter, error) {
}
return &pb.StructuredQuery_Filter{
FilterType: &pb.StructuredQuery_Filter_FieldFilter{
&pb.StructuredQuery_FieldFilter{
FieldFilter: &pb.StructuredQuery_FieldFilter{
Field: fref(f.fieldPath),
Op: op,
Value: val,
@ -342,11 +491,37 @@ func (f filter) toProto() (*pb.StructuredQuery_Filter, error) {
}, nil
}
func unaryOpFor(value interface{}) (pb.StructuredQuery_UnaryFilter_Operator, bool) {
switch {
case value == nil:
return pb.StructuredQuery_UnaryFilter_IS_NULL, true
case isNaN(value):
return pb.StructuredQuery_UnaryFilter_IS_NAN, true
default:
return pb.StructuredQuery_UnaryFilter_OPERATOR_UNSPECIFIED, false
}
}
func isNaN(x interface{}) bool {
switch x := x.(type) {
case float32:
return math.IsNaN(float64(x))
case float64:
return math.IsNaN(x)
default:
return false
}
}
type order struct {
fieldPath FieldPath
dir Direction
}
func (r order) isDocumentID() bool {
return len(r.fieldPath) == 1 && r.fieldPath[0] == DocumentID
}
func (r order) toProto() (*pb.StructuredQuery_Order, error) {
if err := r.fieldPath.validate(); err != nil {
return nil, err
@ -433,7 +608,7 @@ func (it *DocumentIterator) Next() (*DocumentSnapshot, error) {
it.err = err
return nil, err
}
doc, err := newDocumentSnapshot(docRef, res.Document, client)
doc, err := newDocumentSnapshot(docRef, res.Document, client, res.ReadTime)
if err != nil {
it.err = err
return nil, err

View File

@ -15,6 +15,8 @@
package firestore
import (
"math"
"sort"
"testing"
"golang.org/x/net/context"
@ -22,34 +24,87 @@ import (
"cloud.google.com/go/internal/pretty"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
tspb "github.com/golang/protobuf/ptypes/timestamp"
"github.com/golang/protobuf/ptypes/wrappers"
)
func TestFilterToProto(t *testing.T) {
for _, test := range []struct {
in filter
want *pb.StructuredQuery_Filter
}{
{
filter{[]string{"a"}, ">", 1},
&pb.StructuredQuery_Filter{FilterType: &pb.StructuredQuery_Filter_FieldFilter{
FieldFilter: &pb.StructuredQuery_FieldFilter{
Field: &pb.StructuredQuery_FieldReference{FieldPath: "a"},
Op: pb.StructuredQuery_FieldFilter_GREATER_THAN,
Value: intval(1),
},
}},
},
{
filter{[]string{"a"}, "==", nil},
&pb.StructuredQuery_Filter{FilterType: &pb.StructuredQuery_Filter_UnaryFilter{
UnaryFilter: &pb.StructuredQuery_UnaryFilter{
OperandType: &pb.StructuredQuery_UnaryFilter_Field{
Field: &pb.StructuredQuery_FieldReference{FieldPath: "a"},
},
Op: pb.StructuredQuery_UnaryFilter_IS_NULL,
},
}},
},
{
filter{[]string{"a"}, "==", math.NaN()},
&pb.StructuredQuery_Filter{FilterType: &pb.StructuredQuery_Filter_UnaryFilter{
UnaryFilter: &pb.StructuredQuery_UnaryFilter{
OperandType: &pb.StructuredQuery_UnaryFilter_Field{
Field: &pb.StructuredQuery_FieldReference{FieldPath: "a"},
},
Op: pb.StructuredQuery_UnaryFilter_IS_NAN,
},
}},
},
} {
got, err := test.in.toProto()
if err != nil {
t.Fatal(err)
}
if !testEqual(got, test.want) {
t.Errorf("%+v:\ngot\n%v\nwant\n%v", test.in, pretty.Value(got), pretty.Value(test.want))
}
}
}
func TestQueryToProto(t *testing.T) {
c := &Client{}
filtr := func(path []string, op string, val interface{}) *pb.StructuredQuery_Filter {
f, err := filter{path, op, val}.toProto()
if err != nil {
t.Fatal(err)
}
return f
}
c := &Client{projectID: "P", databaseID: "DB"}
coll := c.Collection("C")
q := coll.Query
aFilter, err := filter{[]string{"a"}, ">", 5}.toProto()
if err != nil {
t.Fatal(err)
}
bFilter, err := filter{[]string{"b"}, "<", "foo"}.toProto()
if err != nil {
t.Fatal(err)
}
slashStarFilter, err := filter{[]string{"/", "*"}, ">", 5}.toProto()
if err != nil {
t.Fatal(err)
}
type S struct {
A int `firestore:"a"`
}
docsnap := &DocumentSnapshot{
Ref: coll.Doc("D"),
proto: &pb.Document{
Fields: map[string]*pb.Value{"a": intval(7), "b": intval(8)},
},
}
for _, test := range []struct {
desc string
in Query
want *pb.StructuredQuery
}{
{
in: q.Select(),
desc: "q.Select()",
in: q.Select(),
want: &pb.StructuredQuery{
Select: &pb.StructuredQuery_Projection{
Fields: []*pb.StructuredQuery_FieldReference{fref1("__name__")},
@ -57,7 +112,8 @@ func TestQueryToProto(t *testing.T) {
},
},
{
in: q.Select("a", "b"),
desc: `q.Select("a", "b")`,
in: q.Select("a", "b"),
want: &pb.StructuredQuery{
Select: &pb.StructuredQuery_Projection{
Fields: []*pb.StructuredQuery_FieldReference{fref1("a"), fref1("b")},
@ -65,7 +121,8 @@ func TestQueryToProto(t *testing.T) {
},
},
{
in: q.Select("a", "b").Select("c"), // last wins
desc: `q.Select("a", "b").Select("c")`,
in: q.Select("a", "b").Select("c"), // last wins
want: &pb.StructuredQuery{
Select: &pb.StructuredQuery_Projection{
Fields: []*pb.StructuredQuery_FieldReference{fref1("c")},
@ -73,7 +130,8 @@ func TestQueryToProto(t *testing.T) {
},
},
{
in: q.SelectPaths([]string{"*"}, []string{"/"}),
desc: `q.SelectPaths([]string{"*"}, []string{"/"})`,
in: q.SelectPaths([]string{"*"}, []string{"/"}),
want: &pb.StructuredQuery{
Select: &pb.StructuredQuery_Projection{
Fields: []*pb.StructuredQuery_FieldReference{fref1("*"), fref1("/")},
@ -81,18 +139,35 @@ func TestQueryToProto(t *testing.T) {
},
},
{
desc: `q.Where("a", ">", 5)`,
in: q.Where("a", ">", 5),
want: &pb.StructuredQuery{Where: aFilter},
want: &pb.StructuredQuery{Where: filtr([]string{"a"}, ">", 5)},
},
{
in: q.Where("a", ">", 5).Where("b", "<", "foo"),
desc: `q.Where("a", "==", nil)`,
in: q.Where("a", "==", nil),
want: &pb.StructuredQuery{Where: filtr([]string{"a"}, "==", nil)},
},
{
desc: `q.Where("a", "==", NaN)`,
in: q.Where("a", "==", math.NaN()),
want: &pb.StructuredQuery{Where: filtr([]string{"a"}, "==", math.NaN())},
},
{
desc: `q.Where("a", "==", NaN)`,
in: q.Where("a", "==", float32(math.NaN())),
want: &pb.StructuredQuery{Where: filtr([]string{"a"}, "==", math.NaN())},
},
{
desc: `q.Where("a", ">", 5).Where("b", "<", "foo")`,
in: q.Where("a", ">", 5).Where("b", "<", "foo"),
want: &pb.StructuredQuery{
Where: &pb.StructuredQuery_Filter{
&pb.StructuredQuery_Filter_CompositeFilter{
&pb.StructuredQuery_CompositeFilter{
Op: pb.StructuredQuery_CompositeFilter_AND,
Filters: []*pb.StructuredQuery_Filter{
aFilter, bFilter,
filtr([]string{"a"}, ">", 5), filtr([]string{"b"}, "<", "foo"),
},
},
},
@ -100,11 +175,13 @@ func TestQueryToProto(t *testing.T) {
},
},
{
desc: ` q.WherePath([]string{"/", "*"}, ">", 5)`,
in: q.WherePath([]string{"/", "*"}, ">", 5),
want: &pb.StructuredQuery{Where: slashStarFilter},
want: &pb.StructuredQuery{Where: filtr([]string{"/", "*"}, ">", 5)},
},
{
in: q.OrderBy("b", Asc).OrderBy("a", Desc).OrderByPath([]string{"~"}, Asc),
desc: `q.OrderBy("b", Asc).OrderBy("a", Desc).OrderByPath([]string{"~"}, Asc)`,
in: q.OrderBy("b", Asc).OrderBy("a", Desc).OrderByPath([]string{"~"}, Asc),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("b"), pb.StructuredQuery_ASCENDING},
@ -114,21 +191,24 @@ func TestQueryToProto(t *testing.T) {
},
},
{
in: q.Offset(2).Limit(3),
desc: `q.Offset(2).Limit(3)`,
in: q.Offset(2).Limit(3),
want: &pb.StructuredQuery{
Offset: 2,
Limit: &wrappers.Int32Value{3},
},
},
{
in: q.Offset(2).Limit(3).Limit(4).Offset(5), // last wins
desc: `q.Offset(2).Limit(3).Limit(4).Offset(5)`,
in: q.Offset(2).Limit(3).Limit(4).Offset(5), // last wins
want: &pb.StructuredQuery{
Offset: 5,
Limit: &wrappers.Int32Value{4},
},
},
{
in: q.OrderBy("a", Asc).StartAt(7).EndBefore(9),
desc: `q.OrderBy("a", Asc).StartAt(7).EndBefore(9)`,
in: q.OrderBy("a", Asc).StartAt(7).EndBefore(9),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
@ -144,7 +224,8 @@ func TestQueryToProto(t *testing.T) {
},
},
{
in: q.OrderBy("a", Asc).StartAt(7).EndBefore(9),
desc: `q.OrderBy("a", Asc).StartAt(7).EndAt(9)`,
in: q.OrderBy("a", Asc).StartAt(7).EndAt(9),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
@ -155,12 +236,13 @@ func TestQueryToProto(t *testing.T) {
},
EndAt: &pb.Cursor{
Values: []*pb.Value{intval(9)},
Before: true,
Before: false,
},
},
},
{
in: q.OrderBy("a", Asc).StartAfter(7).EndAt(9),
desc: `q.OrderBy("a", Asc).StartAfter(7).EndAt(9)`,
in: q.OrderBy("a", Asc).StartAfter(7).EndAt(9),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
@ -176,7 +258,25 @@ func TestQueryToProto(t *testing.T) {
},
},
{
in: q.OrderBy("a", Asc).OrderBy("b", Desc).StartAfter(7, 8).EndAt(9, 10),
desc: `q.OrderBy(DocumentID, Asc).StartAfter("foo").EndBefore("bar")`,
in: q.OrderBy(DocumentID, Asc).StartAfter("foo").EndBefore("bar"),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("__name__"), pb.StructuredQuery_ASCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{refval(coll.parentPath + "/documents/C/foo")},
Before: false,
},
EndAt: &pb.Cursor{
Values: []*pb.Value{refval(coll.parentPath + "/documents/C/bar")},
Before: true,
},
},
},
{
desc: `q.OrderBy("a", Asc).OrderBy("b", Desc).StartAfter(7, 8).EndAt(9, 10)`,
in: q.OrderBy("a", Asc).OrderBy("b", Desc).StartAfter(7, 8).EndAt(9, 10),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
@ -194,6 +294,7 @@ func TestQueryToProto(t *testing.T) {
},
{
// last of StartAt/After wins, same for End
desc: `q.OrderBy("a", Asc).StartAfter(1).StartAt(2).EndAt(3).EndBefore(4)`,
in: q.OrderBy("a", Asc).
StartAfter(1).StartAt(2).
EndAt(3).EndBefore(4),
@ -211,14 +312,128 @@ func TestQueryToProto(t *testing.T) {
},
},
},
// Start/End with DocumentSnapshot
// These tests are from the "Document Snapshot Cursors" doc.
{
desc: `q.StartAt(docsnap)`,
in: q.StartAt(docsnap),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("__name__"), pb.StructuredQuery_ASCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{refval(coll.parentPath + "/documents/C/D")},
Before: true,
},
},
},
{
desc: `q.OrderBy("a", Asc).StartAt(docsnap)`,
in: q.OrderBy("a", Asc).StartAt(docsnap),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
{fref1("__name__"), pb.StructuredQuery_ASCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{intval(7), refval(coll.parentPath + "/documents/C/D")},
Before: true,
},
},
},
{
desc: `q.OrderBy("a", Desc).StartAt(docsnap)`,
in: q.OrderBy("a", Desc).StartAt(docsnap),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_DESCENDING},
{fref1("__name__"), pb.StructuredQuery_DESCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{intval(7), refval(coll.parentPath + "/documents/C/D")},
Before: true,
},
},
},
{
desc: `q.OrderBy("a", Desc).OrderBy("b", Asc).StartAt(docsnap)`,
in: q.OrderBy("a", Desc).OrderBy("b", Asc).StartAt(docsnap),
want: &pb.StructuredQuery{
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_DESCENDING},
{fref1("b"), pb.StructuredQuery_ASCENDING},
{fref1("__name__"), pb.StructuredQuery_ASCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{intval(7), intval(8), refval(coll.parentPath + "/documents/C/D")},
Before: true,
},
},
},
{
desc: `q.Where("a", "==", 3).StartAt(docsnap)`,
in: q.Where("a", "==", 3).StartAt(docsnap),
want: &pb.StructuredQuery{
Where: filtr([]string{"a"}, "==", 3),
OrderBy: []*pb.StructuredQuery_Order{
{fref1("__name__"), pb.StructuredQuery_ASCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{refval(coll.parentPath + "/documents/C/D")},
Before: true,
},
},
},
{
desc: `q.Where("a", "<", 3).StartAt(docsnap)`,
in: q.Where("a", "<", 3).StartAt(docsnap),
want: &pb.StructuredQuery{
Where: filtr([]string{"a"}, "<", 3),
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
{fref1("__name__"), pb.StructuredQuery_ASCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{intval(7), refval(coll.parentPath + "/documents/C/D")},
Before: true,
},
},
},
{
desc: `q.Where("b", "==", 1).Where("a", "<", 3).StartAt(docsnap)`,
in: q.Where("b", "==", 1).Where("a", "<", 3).StartAt(docsnap),
want: &pb.StructuredQuery{
Where: &pb.StructuredQuery_Filter{
&pb.StructuredQuery_Filter_CompositeFilter{
&pb.StructuredQuery_CompositeFilter{
Op: pb.StructuredQuery_CompositeFilter_AND,
Filters: []*pb.StructuredQuery_Filter{
filtr([]string{"b"}, "==", 1),
filtr([]string{"a"}, "<", 3),
},
},
},
},
OrderBy: []*pb.StructuredQuery_Order{
{fref1("a"), pb.StructuredQuery_ASCENDING},
{fref1("__name__"), pb.StructuredQuery_ASCENDING},
},
StartAt: &pb.Cursor{
Values: []*pb.Value{intval(7), refval(coll.parentPath + "/documents/C/D")},
Before: true,
},
},
},
} {
got, err := test.in.toProto()
if err != nil {
t.Fatalf("%+v: %v", test.in, err)
t.Errorf("%s: %v", test.desc, err)
continue
}
test.want.From = []*pb.StructuredQuery_CollectionSelector{{CollectionId: "C"}}
if !testEqual(got, test.want) {
t.Errorf("%+v: got\n%v\nwant\n%v", test.in, pretty.Value(got), pretty.Value(test.want))
t.Errorf("%s:\ngot\n%v\nwant\n%v", test.desc, pretty.Value(got), pretty.Value(test.want))
}
}
}
@ -230,7 +445,15 @@ func fref1(s string) *pb.StructuredQuery_FieldReference {
func TestQueryToProtoErrors(t *testing.T) {
st := map[string]interface{}{"a": ServerTimestamp}
del := map[string]interface{}{"a": Delete}
q := (&Client{}).Collection("C").Query
c := &Client{projectID: "P", databaseID: "DB"}
coll := c.Collection("C")
docsnap := &DocumentSnapshot{
Ref: coll.Doc("D"),
proto: &pb.Document{
Fields: map[string]*pb.Value{"a": intval(7)},
},
}
q := coll.Query
for _, query := range []Query{
Query{}, // no collection ID
q.Where("x", "!=", 1), // invalid operator
@ -248,6 +471,11 @@ func TestQueryToProtoErrors(t *testing.T) {
q.Where("x", "==", del), // Delete in filter
q.OrderBy("a", Asc).StartAt(del), // Delete in Start
q.OrderBy("a", Asc).EndAt(del), // Delete in End
q.OrderBy(DocumentID, Asc).StartAt(7), // wrong type for __name__
q.OrderBy(DocumentID, Asc).EndAt(7), // wrong type for __name__
q.OrderBy("b", Asc).StartAt(docsnap), // doc snapshot does not have order-by field
q.StartAt(docsnap).EndAt("x"), // mixed doc snapshot and fields
q.StartAfter("x").EndBefore(docsnap), // mixed doc snapshot and fields
} {
_, err := query.toProto()
if err == nil {
@ -362,10 +590,10 @@ func TestQueryGetAll(t *testing.T) {
Fields: map[string]*pb.Value{"f": intval(1)},
},
}
wantReadTimes := []*tspb.Timestamp{aTimestamp, aTimestamp2}
srv.addRPC(nil, []interface{}{
&pb.RunQueryResponse{Document: wantPBDocs[0]},
&pb.RunQueryResponse{Document: wantPBDocs[1]},
&pb.RunQueryResponse{Document: wantPBDocs[0], ReadTime: aTimestamp},
&pb.RunQueryResponse{Document: wantPBDocs[1], ReadTime: aTimestamp2},
})
gotDocs, err := c.Collection("C").Documents(ctx).GetAll()
if err != nil {
@ -375,7 +603,7 @@ func TestQueryGetAll(t *testing.T) {
t.Errorf("got %d docs, wanted %d", got, want)
}
for i, got := range gotDocs {
want, err := newDocumentSnapshot(c.Doc(docNames[i]), wantPBDocs[i], c)
want, err := newDocumentSnapshot(c.Doc(docNames[i]), wantPBDocs[i], c, wantReadTimes[i])
if err != nil {
t.Fatal(err)
}
@ -387,3 +615,113 @@ func TestQueryGetAll(t *testing.T) {
}
}
}
func TestQueryCompareFunc(t *testing.T) {
mv := func(fields ...interface{}) map[string]*pb.Value {
m := map[string]*pb.Value{}
for i := 0; i < len(fields); i += 2 {
m[fields[i].(string)] = fields[i+1].(*pb.Value)
}
return m
}
snap := func(ref *DocumentRef, fields map[string]*pb.Value) *DocumentSnapshot {
return &DocumentSnapshot{Ref: ref, proto: &pb.Document{Fields: fields}}
}
c := &Client{}
coll := c.Collection("C")
doc1 := coll.Doc("doc1")
doc2 := coll.Doc("doc2")
doc3 := coll.Doc("doc3")
doc4 := coll.Doc("doc4")
for _, test := range []struct {
q Query
in []*DocumentSnapshot
want []*DocumentSnapshot
}{
{
q: coll.OrderBy("foo", Asc),
in: []*DocumentSnapshot{
snap(doc3, mv("foo", intval(2))),
snap(doc4, mv("foo", intval(1))),
snap(doc2, mv("foo", intval(2))),
},
want: []*DocumentSnapshot{
snap(doc4, mv("foo", intval(1))),
snap(doc2, mv("foo", intval(2))),
snap(doc3, mv("foo", intval(2))),
},
},
{
q: coll.OrderBy("foo", Desc),
in: []*DocumentSnapshot{
snap(doc3, mv("foo", intval(2))),
snap(doc4, mv("foo", intval(1))),
snap(doc2, mv("foo", intval(2))),
},
want: []*DocumentSnapshot{
snap(doc3, mv("foo", intval(2))),
snap(doc2, mv("foo", intval(2))),
snap(doc4, mv("foo", intval(1))),
},
},
{
q: coll.OrderBy("foo.bar", Asc),
in: []*DocumentSnapshot{
snap(doc1, mv("foo", mapval(mv("bar", intval(1))))),
snap(doc2, mv("foo", mapval(mv("bar", intval(2))))),
snap(doc3, mv("foo", mapval(mv("bar", intval(2))))),
},
want: []*DocumentSnapshot{
snap(doc1, mv("foo", mapval(mv("bar", intval(1))))),
snap(doc2, mv("foo", mapval(mv("bar", intval(2))))),
snap(doc3, mv("foo", mapval(mv("bar", intval(2))))),
},
},
{
q: coll.OrderBy("foo.bar", Desc),
in: []*DocumentSnapshot{
snap(doc1, mv("foo", mapval(mv("bar", intval(1))))),
snap(doc2, mv("foo", mapval(mv("bar", intval(2))))),
snap(doc3, mv("foo", mapval(mv("bar", intval(2))))),
},
want: []*DocumentSnapshot{
snap(doc3, mv("foo", mapval(mv("bar", intval(2))))),
snap(doc2, mv("foo", mapval(mv("bar", intval(2))))),
snap(doc1, mv("foo", mapval(mv("bar", intval(1))))),
},
},
} {
got := append([]*DocumentSnapshot(nil), test.in...)
sort.Sort(byQuery{test.q.compareFunc(), got})
if diff := testDiff(got, test.want); diff != "" {
t.Errorf("%+v: %s", test.q, diff)
}
}
// Want error on missing field.
q := coll.OrderBy("bar", Asc)
if q.err != nil {
t.Fatalf("bad query: %v", q.err)
}
cf := q.compareFunc()
s := snap(doc1, mv("foo", intval(1)))
if _, err := cf(s, s); err == nil {
t.Error("got nil, want error")
}
}
type byQuery struct {
compare func(d1, d2 *DocumentSnapshot) (int, error)
docs []*DocumentSnapshot
}
func (b byQuery) Len() int { return len(b.docs) }
func (b byQuery) Swap(i, j int) { b.docs[i], b.docs[j] = b.docs[j], b.docs[i] }
func (b byQuery) Less(i, j int) bool {
c, err := b.compare(b.docs[i], b.docs[j])
if err != nil {
panic(err)
}
return c < 0
}

View File

@ -1,11 +0,0 @@
# Copy textproto files in this directory from the source of truth.
SRC=$(GOPATH)/src/github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/testdata
.PHONY: refresh
refresh:
-rm *.textproto
cp $(SRC)/*.textproto .
openssl dgst -sha1 $(SRC)/tests.binprotos > VERSION

View File

@ -1 +1 @@
SHA1(/usr/local/google/home/jba/go/src/github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/testdata/tests.binprotos)= b0fbaaac8664945cb4f5667da092a6f9ededc57e
SHA1(/usr/local/google/home/jba/go/src/github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/testdata/test-suite.binproto)= 3047565564b81726a57d7db719704ea8bf17a9ab

View File

@ -0,0 +1,68 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# When a document snapshot is used, the client appends a __name__ order-by clause
# with the direction of the last order-by clause.
description: "query: cursor methods with a document snapshot, existing orderBy"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
order_by: <
path: <
field: "a"
>
direction: "asc"
>
>
clauses: <
order_by: <
path: <
field: "b"
>
direction: "desc"
>
>
clauses: <
start_after: <
doc_snapshot: <
path: "projects/projectID/databases/(default)/documents/C/D"
json_data: "{\"a\": 7, \"b\": 8}"
>
>
>
query: <
from: <
collection_id: "C"
>
order_by: <
field: <
field_path: "a"
>
direction: ASCENDING
>
order_by: <
field: <
field_path: "b"
>
direction: DESCENDING
>
order_by: <
field: <
field_path: "__name__"
>
direction: DESCENDING
>
start_at: <
values: <
integer_value: 7
>
values: <
integer_value: 8
>
values: <
reference_value: "projects/projectID/databases/(default)/documents/C/D"
>
>
>
>

View File

@ -0,0 +1,76 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# If there is an existing orderBy clause on __name__, no changes are made to the
# list of orderBy clauses.
description: "query: cursor method, doc snapshot, existing orderBy __name__"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
order_by: <
path: <
field: "a"
>
direction: "desc"
>
>
clauses: <
order_by: <
path: <
field: "__name__"
>
direction: "asc"
>
>
clauses: <
start_at: <
doc_snapshot: <
path: "projects/projectID/databases/(default)/documents/C/D"
json_data: "{\"a\": 7, \"b\": 8}"
>
>
>
clauses: <
end_at: <
doc_snapshot: <
path: "projects/projectID/databases/(default)/documents/C/D"
json_data: "{\"a\": 7, \"b\": 8}"
>
>
>
query: <
from: <
collection_id: "C"
>
order_by: <
field: <
field_path: "a"
>
direction: DESCENDING
>
order_by: <
field: <
field_path: "__name__"
>
direction: ASCENDING
>
start_at: <
values: <
integer_value: 7
>
values: <
reference_value: "projects/projectID/databases/(default)/documents/C/D"
>
before: true
>
end_at: <
values: <
integer_value: 7
>
values: <
reference_value: "projects/projectID/databases/(default)/documents/C/D"
>
>
>
>

View File

@ -0,0 +1,53 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# A Where clause using equality doesn't change the implicit orderBy clauses.
description: "query: cursor methods with a document snapshot and an equality where clause"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
where: <
path: <
field: "a"
>
op: "=="
json_value: "3"
>
>
clauses: <
end_at: <
doc_snapshot: <
path: "projects/projectID/databases/(default)/documents/C/D"
json_data: "{\"a\": 7, \"b\": 8}"
>
>
>
query: <
from: <
collection_id: "C"
>
where: <
field_filter: <
field: <
field_path: "a"
>
op: EQUAL
value: <
integer_value: 3
>
>
>
order_by: <
field: <
field_path: "__name__"
>
direction: ASCENDING
>
end_at: <
values: <
reference_value: "projects/projectID/databases/(default)/documents/C/D"
>
>
>
>

View File

@ -0,0 +1,72 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# If there is an OrderBy clause, the inequality Where clause does not result in a
# new OrderBy clause. We still add a __name__ OrderBy clause
description: "query: cursor method, doc snapshot, inequality where clause, and existing orderBy clause"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
order_by: <
path: <
field: "a"
>
direction: "desc"
>
>
clauses: <
where: <
path: <
field: "a"
>
op: "<"
json_value: "4"
>
>
clauses: <
start_at: <
doc_snapshot: <
path: "projects/projectID/databases/(default)/documents/C/D"
json_data: "{\"a\": 7, \"b\": 8}"
>
>
>
query: <
from: <
collection_id: "C"
>
where: <
field_filter: <
field: <
field_path: "a"
>
op: LESS_THAN
value: <
integer_value: 4
>
>
>
order_by: <
field: <
field_path: "a"
>
direction: DESCENDING
>
order_by: <
field: <
field_path: "__name__"
>
direction: DESCENDING
>
start_at: <
values: <
integer_value: 7
>
values: <
reference_value: "projects/projectID/databases/(default)/documents/C/D"
>
before: true
>
>
>

View File

@ -0,0 +1,64 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# A Where clause with an inequality results in an OrderBy clause on that clause's
# path, if there are no other OrderBy clauses.
description: "query: cursor method with a document snapshot and an inequality where clause"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
where: <
path: <
field: "a"
>
op: "<="
json_value: "3"
>
>
clauses: <
end_before: <
doc_snapshot: <
path: "projects/projectID/databases/(default)/documents/C/D"
json_data: "{\"a\": 7, \"b\": 8}"
>
>
>
query: <
from: <
collection_id: "C"
>
where: <
field_filter: <
field: <
field_path: "a"
>
op: LESS_THAN_OR_EQUAL
value: <
integer_value: 3
>
>
>
order_by: <
field: <
field_path: "a"
>
direction: ASCENDING
>
order_by: <
field: <
field_path: "__name__"
>
direction: ASCENDING
>
end_at: <
values: <
integer_value: 7
>
values: <
reference_value: "projects/projectID/databases/(default)/documents/C/D"
>
before: true
>
>
>

View File

@ -0,0 +1,34 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# When a document snapshot is used, the client appends a __name__ order-by clause.
description: "query: cursor methods with a document snapshot"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
start_at: <
doc_snapshot: <
path: "projects/projectID/databases/(default)/documents/C/D"
json_data: "{\"a\": 7, \"b\": 8}"
>
>
>
query: <
from: <
collection_id: "C"
>
order_by: <
field: <
field_path: "__name__"
>
direction: ASCENDING
>
start_at: <
values: <
reference_value: "projects/projectID/databases/(default)/documents/C/D"
>
before: true
>
>
>

View File

@ -0,0 +1,16 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# If a cursor method with a list of values is provided, there must be at least as
# many explicit orderBy clauses as values.
description: "query: cursor method without orderBy"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
start_at: <
json_values: "2"
>
>
is_error: true
>

View File

@ -0,0 +1,50 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# Cursor methods take the same number of values as there are OrderBy clauses.
description: "query: StartAt/EndBefore with values"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
order_by: <
path: <
field: "a"
>
direction: "asc"
>
>
clauses: <
start_at: <
json_values: "7"
>
>
clauses: <
end_before: <
json_values: "9"
>
>
query: <
from: <
collection_id: "C"
>
order_by: <
field: <
field_path: "a"
>
direction: ASCENDING
>
start_at: <
values: <
integer_value: 7
>
before: true
>
end_at: <
values: <
integer_value: 9
>
before: true
>
>
>

View File

@ -0,0 +1,48 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# Cursor methods take the same number of values as there are OrderBy clauses.
description: "query: StartAfter/EndAt with values"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
order_by: <
path: <
field: "a"
>
direction: "asc"
>
>
clauses: <
start_after: <
json_values: "7"
>
>
clauses: <
end_at: <
json_values: "9"
>
>
query: <
from: <
collection_id: "C"
>
order_by: <
field: <
field_path: "a"
>
direction: ASCENDING
>
start_at: <
values: <
integer_value: 7
>
>
end_at: <
values: <
integer_value: 9
>
>
>
>

View File

@ -0,0 +1,71 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# Cursor methods take the same number of values as there are OrderBy clauses.
description: "query: Start/End with two values"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
order_by: <
path: <
field: "a"
>
direction: "asc"
>
>
clauses: <
order_by: <
path: <
field: "b"
>
direction: "desc"
>
>
clauses: <
start_at: <
json_values: "7"
json_values: "8"
>
>
clauses: <
end_at: <
json_values: "9"
json_values: "10"
>
>
query: <
from: <
collection_id: "C"
>
order_by: <
field: <
field_path: "a"
>
direction: ASCENDING
>
order_by: <
field: <
field_path: "b"
>
direction: DESCENDING
>
start_at: <
values: <
integer_value: 7
>
values: <
integer_value: 8
>
before: true
>
end_at: <
values: <
integer_value: 9
>
values: <
integer_value: 10
>
>
>
>

View File

@ -0,0 +1,50 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# Cursor values corresponding to a __name__ field take the document path relative
# to the query's collection.
description: "query: cursor methods with __name__"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
order_by: <
path: <
field: "__name__"
>
direction: "asc"
>
>
clauses: <
start_after: <
json_values: "\"D1\""
>
>
clauses: <
end_before: <
json_values: "\"D2\""
>
>
query: <
from: <
collection_id: "C"
>
order_by: <
field: <
field_path: "__name__"
>
direction: ASCENDING
>
start_at: <
values: <
reference_value: "projects/projectID/databases/(default)/documents/C/D1"
>
>
end_at: <
values: <
reference_value: "projects/projectID/databases/(default)/documents/C/D2"
>
before: true
>
>
>

View File

@ -0,0 +1,60 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# When multiple Start* or End* calls occur, the values of the last one are used.
description: "query: cursor methods, last one wins"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
order_by: <
path: <
field: "a"
>
direction: "asc"
>
>
clauses: <
start_after: <
json_values: "1"
>
>
clauses: <
start_at: <
json_values: "2"
>
>
clauses: <
end_at: <
json_values: "3"
>
>
clauses: <
end_before: <
json_values: "4"
>
>
query: <
from: <
collection_id: "C"
>
order_by: <
field: <
field_path: "a"
>
direction: ASCENDING
>
start_at: <
values: <
integer_value: 2
>
before: true
>
end_at: <
values: <
integer_value: 4
>
before: true
>
>
>

View File

@ -0,0 +1,23 @@
# DO NOT MODIFY. This file was generated by
# github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore/cmd/generate-firestore-tests/generate-firestore-tests.go.
# Sentinel values are not permitted in queries.
description: "query: Delete in cursor method"
query: <
coll_path: "projects/projectID/databases/(default)/documents/C"
clauses: <
order_by: <
path: <
field: "a"
>
direction: "asc"
>
>
clauses: <
end_before: <
json_values: "\"Delete\""
>
>
is_error: true
>

Some files were not shown because too many files have changed in this diff Show More