Move to vendor

Signed-off-by: Olivier Gambier <olivier@docker.com>
This commit is contained in:
Olivier Gambier 2016-03-18 14:07:13 -07:00
parent c8d8e7e357
commit 77e69b9cf3
1268 changed files with 34 additions and 24 deletions

11
vendor/google.golang.org/cloud/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,11 @@
sudo: false
language: go
go:
- 1.4
- tip
install:
- go get -v google.golang.org/cloud/...
script:
- openssl aes-256-cbc -K $encrypted_912ff8fa81ad_key -iv $encrypted_912ff8fa81ad_iv -in key.json.enc -out key.json -d
- GCLOUD_TESTS_GOLANG_PROJECT_ID="dulcet-port-762" GCLOUD_TESTS_GOLANG_KEY="$(pwd)/key.json"
go test -v -tags=integration google.golang.org/cloud/...

12
vendor/google.golang.org/cloud/AUTHORS generated vendored Normal file
View file

@ -0,0 +1,12 @@
# This is the official list of cloud authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
# Names should be added to this file as:
# Name or Organization <email address>
# The email address is not required for organizations.
Google Inc.
Palm Stone Games, Inc.
Péter Szilágyi <peterke@gmail.com>
Tyler Treat <ttreat31@gmail.com>

114
vendor/google.golang.org/cloud/CONTRIBUTING.md generated vendored Normal file
View file

@ -0,0 +1,114 @@
# Contributing
1. Sign one of the contributor license agreements below.
1. `go get golang.org/x/review/git-codereview` to install the code reviewing tool.
1. Get the cloud package by running `go get -d google.golang.org/cloud`.
1. If you have already checked out the source, make sure that the remote git
origin is https://code.googlesource.com/gocloud:
git remote set-url origin https://code.googlesource.com/gocloud
1. Make changes and create a change by running `git codereview change <name>`,
provide a command message, and use `git codereview mail` to create a Gerrit CL.
1. Keep amending to the change and mail as your recieve feedback.
## Integration Tests
Additional to the unit tests, you may run the integration test suite.
To run the integrations tests, creating and configuration of a project in the
Google Developers Console is required. Once you create a project, set the
following environment variables to be able to run the against the actual APIs.
- **GCLOUD_TESTS_GOLANG_PROJECT_ID**: Developers Console project's ID (e.g. bamboo-shift-455)
- **GCLOUD_TESTS_GOLANG_KEY**: The path to the JSON key file.
Create a storage bucket with the same name as the project id set in **GCLOUD_TESTS_GOLANG_PROJECT_ID**.
The storage integration test will create and delete some objects in this bucket.
Install the [gcloud command-line tool][gcloudcli] to your machine and use it
to create the indexes used in the datastore integration tests with indexes
found in `datastore/testdata/index.yaml`:
From the project's root directory:
``` sh
# Install the app component
$ gcloud components update app
# Set the default project in your env
$ gcloud config set project $GCLOUD_TESTS_GOLANG_PROJECT_ID
# Authenticate the gcloud tool with your account
$ gcloud auth login
# Create the indexes
$ gcloud preview datastore create-indexes datastore/testdata/index.yaml
```
You can run the integration tests by running:
``` sh
$ go test -v -tags=integration google.golang.org/cloud/...
```
## Contributor License Agreements
Before we can accept your pull requests you'll need to sign a Contributor
License Agreement (CLA):
- **If you are an individual writing original source code** and **you own the
- intellectual property**, then you'll need to sign an [individual CLA][indvcla].
- **If you work for a company that wants to allow you to contribute your work**,
then you'll need to sign a [corporate CLA][corpcla].
You can sign these electronically (just scroll to the bottom). After that,
we'll be able to accept your pull requests.
## Contributor Code of Conduct
As contributors and maintainers of this project,
and in the interest of fostering an open and welcoming community,
we pledge to respect all people who contribute through reporting issues,
posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project
a harassment-free experience for everyone,
regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information,
such as physical or electronic
addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct.
By adopting this Code of Conduct,
project maintainers commit themselves to fairly and consistently
applying these principles to every aspect of managing this project.
Project maintainers who do not follow or enforce the Code of Conduct
may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported by opening an issue
or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
[gcloudcli]: https://developers.google.com/cloud/sdk/gcloud/
[indvcla]: https://developers.google.com/open-source/cla/individual
[corpcla]: https://developers.google.com/open-source/cla/corporate

24
vendor/google.golang.org/cloud/CONTRIBUTORS generated vendored Normal file
View file

@ -0,0 +1,24 @@
# People who have agreed to one of the CLAs and can contribute patches.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# https://developers.google.com/open-source/cla/individual
# https://developers.google.com/open-source/cla/corporate
#
# Names should be added to this file as:
# Name <email address>
# Keep the list alphabetically sorted.
Andrew Gerrand <adg@golang.org>
Brad Fitzpatrick <bradfitz@golang.org>
Burcu Dogan <jbd@google.com>
Dave Day <djd@golang.org>
David Symonds <dsymonds@golang.org>
Glenn Lewis <gmlewis@google.com>
Johan Euphrosine <proppy@google.com>
Luna Duclos <luna.duclos@palmstonegames.com>
Michael McGreevy <mcgreevy@golang.org>
Péter Szilágyi <peterke@gmail.com>
Tyler Treat <ttreat31@gmail.com>

202
vendor/google.golang.org/cloud/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2014 Google Inc.
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.

135
vendor/google.golang.org/cloud/README.md generated vendored Normal file
View file

@ -0,0 +1,135 @@
# Google Cloud for Go
[![Build Status](https://travis-ci.org/GoogleCloudPlatform/gcloud-golang.svg?branch=master)](https://travis-ci.org/GoogleCloudPlatform/gcloud-golang)
**NOTE:** These packages are experimental, and may occasionally make
backwards-incompatible changes.
**NOTE:** Github repo is a mirror of [https://code.googlesource.com/gocloud](https://code.googlesource.com/gocloud).
Go packages for Google Cloud Platform services. Supported APIs include:
* Google Cloud Datastore
* Google Cloud Storage
* Google Cloud Pub/Sub
* Google Cloud Container Engine
``` go
import "google.golang.org/cloud"
```
Documentation and examples are available at
[https://godoc.org/google.golang.org/cloud](https://godoc.org/google.golang.org/cloud).
## Authorization
Authorization, throughout the package, is delegated to the godoc.org/golang.org/x/oauth2.
Refer to the [godoc documentation](https://godoc.org/golang.org/x/oauth2)
for examples on using oauth2 with the Cloud package.
## Google Cloud Datastore
[Google Cloud Datastore][cloud-datastore] ([docs][cloud-datastore-docs]) is a fully
managed, schemaless database for storing non-relational data. Cloud Datastore
automatically scales with your users and supports ACID transactions, high availability
of reads and writes, strong consistency for reads and ancestor queries, and eventual
consistency for all other queries.
Follow the [activation instructions][cloud-datastore-activation] to use the Google
Cloud Datastore API with your project.
[https://godoc.org/google.golang.org/cloud/datastore](https://godoc.org/google.golang.org/cloud/datastore)
```go
type Post struct {
Title string
Body string `datastore:",noindex"`
PublishedAt time.Time
}
keys := []*datastore.Key{
datastore.NewKey(ctx, "Post", "post1", 0, nil),
datastore.NewKey(ctx, "Post", "post2", 0, nil),
}
posts := []*Post{
{Title: "Post 1", Body: "...", PublishedAt: time.Now()},
{Title: "Post 2", Body: "...", PublishedAt: time.Now()},
}
if _, err := datastore.PutMulti(ctx, keys, posts); err != nil {
log.Println(err)
}
```
## Google Cloud Storage
[Google Cloud Storage][cloud-storage] ([docs][cloud-storage-docs]) allows you to store
data on Google infrastructure with very high reliability, performance and availability,
and can be used to distribute large data objects to users via direct download.
[https://godoc.org/google.golang.org/cloud/storage](https://godoc.org/google.golang.org/cloud/storage)
```go
// Read the object1 from bucket.
rc, err := storage.NewReader(ctx, "bucket", "object1")
if err != nil {
log.Fatal(err)
}
slurp, err := ioutil.ReadAll(rc)
rc.Close()
if err != nil {
log.Fatal(err)
}
```
## Google Cloud Pub/Sub (Alpha)
> Google Cloud Pub/Sub is in **Alpha status**. As a result, it might change in
> backward-incompatible ways and is not recommended for production use. It is not
> subject to any SLA or deprecation policy.
[Google Cloud Pub/Sub][cloud-pubsub] ([docs][cloud-pubsub-docs]) allows you to connect
your services with reliable, many-to-many, asynchronous messaging hosted on Google's
infrastructure. Cloud Pub/Sub automatically scales as you need it and provides a foundation
for building your own robust, global services.
[https://godoc.org/google.golang.org/cloud/pubsub](https://godoc.org/google.golang.org/cloud/pubsub)
```go
// Publish "hello world" on topic1.
msgIDs, err := pubsub.Publish(ctx, "topic1", &pubsub.Message{
Data: []byte("hello world"),
})
if err != nil {
log.Println(err)
}
// Pull messages via subscription1.
msgs, err := pubsub.Pull(ctx, "subscription1", 1)
if err != nil {
log.Println(err)
}
```
## Contributing
Contributions are welcome. Please, see the
[CONTRIBUTING](https://github.com/GoogleCloudPlatform/gcloud-golang/blob/master/CONTRIBUTING.md)
document for details. We're using Gerrit for our code reviews. Please don't open pull
requests against this repo, new pull requests will be automatically closed.
Please note that this project is released with a Contributor Code of Conduct.
By participating in this project you agree to abide by its terms.
See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/gcloud-golang/blob/master/CONTRIBUTING.md#contributor-code-of-conduct)
for more information.
[cloud-datastore]: https://cloud.google.com/datastore/
[cloud-datastore-docs]: https://cloud.google.com/datastore/docs
[cloud-datastore-activation]: https://cloud.google.com/datastore/docs/activate
[cloud-pubsub]: https://cloud.google.com/pubsub/
[cloud-pubsub-docs]: https://cloud.google.com/pubsub/docs
[cloud-storage]: https://cloud.google.com/storage/
[cloud-storage-docs]: https://cloud.google.com/storage/docs/overview
[cloud-storage-create-bucket]: https://cloud.google.com/storage/docs/cloud-console#_creatingbuckets

147
vendor/google.golang.org/cloud/bigquery/bigquery.go generated vendored Normal file
View file

@ -0,0 +1,147 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
// TODO(mcgreevy): support dry-run mode when creating jobs.
import (
"fmt"
"net/http"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
// A Source is a source of data for the Copy function.
type Source interface {
implementsSource()
}
// A Destination is a destination of data for the Copy function.
type Destination interface {
implementsDestination()
}
// An Option is an optional argument to Copy.
type Option interface {
implementsOption()
}
// A ReadSource is a source of data for the Read function.
type ReadSource interface {
implementsReadSource()
}
// A ReadOption is an optional argument to Read.
type ReadOption interface {
customizeRead(conf *pagingConf)
}
const Scope = "https://www.googleapis.com/auth/bigquery"
// Client may be used to perform BigQuery operations.
type Client struct {
service service
projectID string
}
// Note: many of the methods on *Client appear in the various *_op.go source files.
// NewClient constructs a new Client which can perform BigQuery operations.
// Operations performed via the client are billed to the specified GCP project.
// The supplied http.Client is used for making requests to the BigQuery server and must be capable of
// authenticating requests with Scope.
func NewClient(client *http.Client, projectID string) (*Client, error) {
bqService, err := newBigqueryService(client)
if err != nil {
return nil, fmt.Errorf("constructing bigquery client: %v", err)
}
c := &Client{
service: bqService,
projectID: projectID,
}
return c, nil
}
// initJobProto creates and returns a bigquery Job proto.
// The proto is customized using any jobOptions in options.
// The list of Options is returned with the jobOptions removed.
func initJobProto(projectID string, options []Option) (*bq.Job, []Option) {
job := &bq.Job{}
var other []Option
for _, opt := range options {
if o, ok := opt.(jobOption); ok {
o.customizeJob(job, projectID)
} else {
other = append(other, opt)
}
}
return job, other
}
// Copy starts a BigQuery operation to copy data from a Source to a Destination.
func (c *Client) Copy(ctx context.Context, dst Destination, src Source, options ...Option) (*Job, error) {
switch dst := dst.(type) {
case *Table:
switch src := src.(type) {
case *GCSReference:
return c.load(ctx, dst, src, options)
case *Table:
return c.cp(ctx, dst, Tables{src}, options)
case Tables:
return c.cp(ctx, dst, src, options)
case *Query:
return c.query(ctx, dst, src, options)
}
case *GCSReference:
if src, ok := src.(*Table); ok {
return c.extract(ctx, dst, src, options)
}
}
return nil, fmt.Errorf("no Copy operation matches dst/src pair: dst: %T ; src: %T", dst, src)
}
// Read fetches data from a ReadSource and returns the data via an Iterator.
func (c *Client) Read(ctx context.Context, src ReadSource, options ...ReadOption) (*Iterator, error) {
switch src := src.(type) {
case *Job:
return c.readQueryResults(src, options)
case *Query:
return c.executeQuery(ctx, src, options...)
case *Table:
return c.readTable(src, options)
}
return nil, fmt.Errorf("src (%T) does not support the Read operation", src)
}
// executeQuery submits a query for execution and returns the results via an Iterator.
func (c *Client) executeQuery(ctx context.Context, q *Query, options ...ReadOption) (*Iterator, error) {
dest := &Table{}
job, err := c.Copy(ctx, dest, q, WriteTruncate)
if err != nil {
return nil, err
}
return c.Read(ctx, job, options...)
}
func (c *Client) Dataset(id string) *Dataset {
return &Dataset{
id: id,
client: c,
}
}

47
vendor/google.golang.org/cloud/bigquery/copy_op.go generated vendored Normal file
View file

@ -0,0 +1,47 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"fmt"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
type copyOption interface {
customizeCopy(conf *bq.JobConfigurationTableCopy, projectID string)
}
func (c *Client) cp(ctx context.Context, dst *Table, src Tables, options []Option) (*Job, error) {
job, options := initJobProto(c.projectID, options)
payload := &bq.JobConfigurationTableCopy{}
dst.customizeCopyDst(payload, c.projectID)
src.customizeCopySrc(payload, c.projectID)
for _, opt := range options {
o, ok := opt.(copyOption)
if !ok {
return nil, fmt.Errorf("option (%#v) not applicable to dst/src pair: dst: %T ; src: %T", opt, dst, src)
}
o.customizeCopy(payload, c.projectID)
}
job.Configuration = &bq.JobConfiguration{
Copy: payload,
}
return c.service.insertJob(ctx, job, c.projectID)
}

104
vendor/google.golang.org/cloud/bigquery/copy_test.go generated vendored Normal file
View file

@ -0,0 +1,104 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"reflect"
"testing"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
func defaultCopyJob() *bq.Job {
return &bq.Job{
Configuration: &bq.JobConfiguration{
Copy: &bq.JobConfigurationTableCopy{
DestinationTable: &bq.TableReference{
ProjectId: "d-project-id",
DatasetId: "d-dataset-id",
TableId: "d-table-id",
},
SourceTables: []*bq.TableReference{
{
ProjectId: "s-project-id",
DatasetId: "s-dataset-id",
TableId: "s-table-id",
},
},
},
},
}
}
func TestCopy(t *testing.T) {
testCases := []struct {
dst *Table
src Tables
options []Option
want *bq.Job
}{
{
dst: &Table{
ProjectID: "d-project-id",
DatasetID: "d-dataset-id",
TableID: "d-table-id",
},
src: Tables{
{
ProjectID: "s-project-id",
DatasetID: "s-dataset-id",
TableID: "s-table-id",
},
},
want: defaultCopyJob(),
},
{
dst: &Table{
ProjectID: "d-project-id",
DatasetID: "d-dataset-id",
TableID: "d-table-id",
},
src: Tables{
{
ProjectID: "s-project-id",
DatasetID: "s-dataset-id",
TableID: "s-table-id",
},
},
options: []Option{CreateNever, WriteTruncate},
want: func() *bq.Job {
j := defaultCopyJob()
j.Configuration.Copy.CreateDisposition = "CREATE_NEVER"
j.Configuration.Copy.WriteDisposition = "WRITE_TRUNCATE"
return j
}(),
},
}
for _, tc := range testCases {
s := &testService{}
c := &Client{
service: s,
}
if _, err := c.Copy(context.Background(), tc.dst, tc.src, tc.options...); err != nil {
t.Errorf("err calling cp: %v", err)
continue
}
if !reflect.DeepEqual(s.Job, tc.want) {
t.Errorf("copying: got:\n%v\nwant:\n%v", s.Job, tc.want)
}
}
}

View file

@ -0,0 +1,55 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"reflect"
"testing"
"time"
"golang.org/x/net/context"
)
type createTableRecorder struct {
conf *createTableConf
service
}
func (rec *createTableRecorder) createTable(ctx context.Context, conf *createTableConf) error {
rec.conf = conf
return nil
}
func TestCreateTableOptions(t *testing.T) {
s := &createTableRecorder{}
c := &Client{
service: s,
}
exp := time.Now()
q := "query"
if _, err := c.CreateTable(context.Background(), "p", "d", "t", TableExpiration(exp), ViewQuery(q)); err != nil {
t.Fatalf("err calling CreateTable: %v", err)
}
want := createTableConf{
projectID: "p",
datasetID: "d",
tableID: "t",
expiration: exp,
viewQuery: q,
}
if !reflect.DeepEqual(*s.conf, want) {
t.Errorf("createTableConf: got:\n%v\nwant:\n%v", *s.conf, want)
}
}

41
vendor/google.golang.org/cloud/bigquery/dataset.go generated vendored Normal file
View file

@ -0,0 +1,41 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import "golang.org/x/net/context"
// Dataset is a reference to a BigQuery dataset.
type Dataset struct {
id string
client *Client
}
// ListTables returns a list of all the tables contained in the Dataset.
func (d *Dataset) ListTables(ctx context.Context) ([]*Table, error) {
var tables []*Table
err := getPages("", func(pageToken string) (string, error) {
ts, tok, err := d.client.service.listTables(ctx, d.client.projectID, d.id, pageToken)
if err == nil {
tables = append(tables, ts...)
}
return tok, err
})
if err != nil {
return nil, err
}
return tables, nil
}

105
vendor/google.golang.org/cloud/bigquery/dataset_test.go generated vendored Normal file
View file

@ -0,0 +1,105 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"errors"
"reflect"
"testing"
"golang.org/x/net/context"
)
// readServiceStub services read requests by returning data from an in-memory list of values.
type listTablesServiceStub struct {
expectedProject, expectedDataset string
values [][]*Table // contains pages of tables.
pageTokens map[string]string // maps incoming page token to returned page token.
service
}
func (s *listTablesServiceStub) listTables(ctx context.Context, projectID, datasetID, pageToken string) ([]*Table, string, error) {
if projectID != s.expectedProject {
return nil, "", errors.New("wrong project id")
}
if datasetID != s.expectedDataset {
return nil, "", errors.New("wrong dataset id")
}
tables := s.values[0]
s.values = s.values[1:]
return tables, s.pageTokens[pageToken], nil
}
func TestListTables(t *testing.T) {
t1 := &Table{ProjectID: "p1", DatasetID: "d1", TableID: "t1"}
t2 := &Table{ProjectID: "p1", DatasetID: "d1", TableID: "t2"}
t3 := &Table{ProjectID: "p1", DatasetID: "d1", TableID: "t3"}
testCases := []struct {
data [][]*Table
pageTokens map[string]string
want []*Table
}{
{
data: [][]*Table{{t1, t2}, {t3}},
pageTokens: map[string]string{"": "a", "a": ""},
want: []*Table{t1, t2, t3},
},
{
data: [][]*Table{{t1, t2}, {t3}},
pageTokens: map[string]string{"": ""}, // no more pages after first one.
want: []*Table{t1, t2},
},
}
for _, tc := range testCases {
c := &Client{
service: &listTablesServiceStub{
expectedProject: "x",
expectedDataset: "y",
values: tc.data,
pageTokens: tc.pageTokens,
},
projectID: "x",
}
got, err := c.Dataset("y").ListTables(context.Background())
if err != nil {
t.Errorf("err calling ListTables: %v", err)
continue
}
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("reading: got:\n%v\nwant:\n%v", got, tc.want)
}
}
}
func TestListTablesError(t *testing.T) {
c := &Client{
service: &listTablesServiceStub{
expectedProject: "x",
expectedDataset: "y",
},
projectID: "x",
}
// Test that service read errors are propagated back to the caller.
// Passing "not y" as the dataset id will cause the service to return an error.
_, err := c.Dataset("not y").ListTables(context.Background())
if err == nil {
// Read should not return an error; only Err should.
t.Errorf("ListTables expected: non-nil err, got: nil")
}
}

18
vendor/google.golang.org/cloud/bigquery/doc.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package bigquery provides a client for the BigQuery service.
//
// Note: This package is a work-in-progress. Backwards-incompatible changes should be expected.
package bigquery // import "google.golang.org/cloud/bigquery"

42
vendor/google.golang.org/cloud/bigquery/error.go generated vendored Normal file
View file

@ -0,0 +1,42 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"fmt"
bq "google.golang.org/api/bigquery/v2"
)
// An Error contains detailed information about an error encountered while processing a job.
type Error struct {
// Mirrors bq.ErrorProto, but drops DebugInfo
Location, Message, Reason string
}
func (e Error) Error() string {
return fmt.Sprintf("{Location: %q; Message: %q; Reason: %q}", e.Location, e.Message, e.Reason)
}
func errorFromErrorProto(ep *bq.ErrorProto) *Error {
if ep == nil {
return nil
}
return &Error{
Location: ep.Location,
Message: ep.Message,
Reason: ep.Reason,
}
}

59
vendor/google.golang.org/cloud/bigquery/extract_op.go generated vendored Normal file
View file

@ -0,0 +1,59 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"fmt"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
type extractOption interface {
customizeExtract(conf *bq.JobConfigurationExtract, projectID string)
}
// DisableHeader returns an Option that disables the printing of a header row in exported data.
func DisableHeader() Option { return disableHeader{} }
type disableHeader struct{}
func (opt disableHeader) implementsOption() {}
func (opt disableHeader) customizeExtract(conf *bq.JobConfigurationExtract, projectID string) {
f := false
conf.PrintHeader = &f
}
func (c *Client) extract(ctx context.Context, dst *GCSReference, src *Table, options []Option) (*Job, error) {
job, options := initJobProto(c.projectID, options)
payload := &bq.JobConfigurationExtract{}
dst.customizeExtractDst(payload, c.projectID)
src.customizeExtractSrc(payload, c.projectID)
for _, opt := range options {
o, ok := opt.(extractOption)
if !ok {
return nil, fmt.Errorf("option (%#v) not applicable to dst/src pair: dst: %T ; src: %T", opt, dst, src)
}
o.customizeExtract(payload, c.projectID)
}
job.Configuration = &bq.JobConfiguration{
Extract: payload,
}
return c.service.insertJob(ctx, job, c.projectID)
}

View file

@ -0,0 +1,97 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"reflect"
"testing"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
func defaultExtractJob() *bq.Job {
return &bq.Job{
Configuration: &bq.JobConfiguration{
Extract: &bq.JobConfigurationExtract{
SourceTable: &bq.TableReference{
ProjectId: "project-id",
DatasetId: "dataset-id",
TableId: "table-id",
},
DestinationUris: []string{"uri"},
},
},
}
}
func TestExtract(t *testing.T) {
testCases := []struct {
dst *GCSReference
src *Table
options []Option
want *bq.Job
}{
{
dst: defaultGCS,
src: defaultTable,
want: defaultExtractJob(),
},
{
dst: defaultGCS,
src: defaultTable,
options: []Option{
DisableHeader(),
},
want: func() *bq.Job {
j := defaultExtractJob()
f := false
j.Configuration.Extract.PrintHeader = &f
return j
}(),
},
{
dst: &GCSReference{
uris: []string{"uri"},
Compression: Gzip,
DestinationFormat: JSON,
FieldDelimiter: "\t",
},
src: defaultTable,
want: func() *bq.Job {
j := defaultExtractJob()
j.Configuration.Extract.Compression = "GZIP"
j.Configuration.Extract.DestinationFormat = "NEWLINE_DELIMITED_JSON"
j.Configuration.Extract.FieldDelimiter = "\t"
return j
}(),
},
}
for _, tc := range testCases {
s := &testService{}
c := &Client{
service: s,
}
if _, err := c.Copy(context.Background(), tc.dst, tc.src, tc.options...); err != nil {
t.Errorf("err calling extract: %v", err)
continue
}
if !reflect.DeepEqual(s.Job, tc.want) {
t.Errorf("extracting: got:\n%v\nwant:\n%v", s.Job, tc.want)
}
}
}

112
vendor/google.golang.org/cloud/bigquery/gcs.go generated vendored Normal file
View file

@ -0,0 +1,112 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import bq "google.golang.org/api/bigquery/v2"
// GCSReference is a reference to one or more Google Cloud Storage objects, which together constitute
// an input or output to a BigQuery operation.
type GCSReference struct {
uris []string
// FieldDelimiter is the separator for fields in a CSV file, used when loading or exporting data.
// The default is ",".
FieldDelimiter string
// The number of rows at the top of a CSV file that BigQuery will skip when loading the data.
SkipLeadingRows int64
// SourceFormat is the format of the GCS data to be loaded into BigQuery.
// Allowed values are: CSV, JSON, DatastoreBackup. The default is CSV.
SourceFormat DataFormat
// Only used when loading data.
Encoding Encoding
// Quote is the value used to quote data sections in a CSV file.
// The default quotation character is the double quote ("), which is used if both Quote and ForceZeroQuote are unset.
// To specify that no character should be interpreted as a quotation character, set ForceZeroQuote to true.
// Only used when loading data.
Quote string
ForceZeroQuote bool
// DestinationFormat is the format to use when writing exported files.
// Allowed values are: CSV, Avro, JSON. The default is CSV.
// CSV is not supported for tables with nested or repeated fields.
DestinationFormat DataFormat
// Only used when writing data. Default is None.
Compression Compression
}
func (gcs *GCSReference) implementsSource() {}
func (gcs *GCSReference) implementsDestination() {}
// NewGCSReference constructs a reference to one or more Google Cloud Storage objects, which together constitute a data source or destination.
// In the simple case, a single URI in the form gs://bucket/object may refer to a single GCS object.
// Data may also be split into mutiple files, if multiple URIs or URIs containing wildcards are provided.
// Each URI may contain one '*' wildcard character, which (if present) must come after the bucket name.
// For more information about the treatment of wildcards and multiple URIs,
// see https://cloud.google.com/bigquery/exporting-data-from-bigquery#exportingmultiple
func (c *Client) NewGCSReference(uri ...string) *GCSReference {
return &GCSReference{uris: uri}
}
type DataFormat string
const (
CSV DataFormat = "CSV"
Avro DataFormat = "AVRO"
JSON DataFormat = "NEWLINE_DELIMITED_JSON"
DatastoreBackup DataFormat = "DATASTORE_BACKUP"
)
// Encoding specifies the character encoding of data to be loaded into BigQuery.
// See https://cloud.google.com/bigquery/docs/reference/v2/jobs#configuration.load.encoding
// for more details about how this is used.
type Encoding string
const (
UTF_8 Encoding = "UTF-8"
ISO_8859_1 Encoding = "ISO-8859-1"
)
// Compression is the type of compression to apply when writing data to Google Cloud Storage.
type Compression string
const (
None Compression = "NONE"
Gzip Compression = "GZIP"
)
func (gcs *GCSReference) customizeLoadSrc(conf *bq.JobConfigurationLoad, projectID string) {
conf.SourceUris = gcs.uris
conf.SkipLeadingRows = gcs.SkipLeadingRows
conf.SourceFormat = string(gcs.SourceFormat)
conf.Encoding = string(gcs.Encoding)
conf.FieldDelimiter = gcs.FieldDelimiter
if gcs.ForceZeroQuote {
quote := ""
conf.Quote = &quote
} else if gcs.Quote != "" {
conf.Quote = &gcs.Quote
}
}
func (gcs *GCSReference) customizeExtractDst(conf *bq.JobConfigurationExtract, projectID string) {
conf.DestinationUris = gcs.uris
conf.Compression = string(gcs.Compression)
conf.DestinationFormat = string(gcs.DestinationFormat)
conf.FieldDelimiter = gcs.FieldDelimiter
}

168
vendor/google.golang.org/cloud/bigquery/iterator.go generated vendored Normal file
View file

@ -0,0 +1,168 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"errors"
"fmt"
"golang.org/x/net/context"
)
// A pageFetcher returns a page of rows, starting from the row specified by token.
type pageFetcher interface {
fetch(ctx context.Context, c *Client, token string) (*readDataResult, error)
}
// Iterator provides access to the result of a BigQuery lookup.
// Next must be called before the first call to Get.
type Iterator struct {
c *Client
err error // contains any error encountered during calls to Next.
// Once Next has been called at least once, rs contains the current
// page of data and nextToken contains the token for fetching the next
// page (empty if there is no more data to be fetched).
rs [][]Value
nextToken string
// The remaining fields contain enough information to fetch the current
// page of data, and determine which row of data from this page is the
// current row.
pf pageFetcher
pageToken string
// The offset from the start of the current page to the current row.
// For a new iterator, this is -1.
offset int64
}
func newIterator(c *Client, pf pageFetcher) *Iterator {
return &Iterator{
c: c,
pf: pf,
offset: -1,
}
}
// fetchPage loads the current page of data from the server.
// The contents of rs and nextToken are replaced with the loaded data.
// If there is an error while fetching, the error is stored in it.err and false is returned.
func (it *Iterator) fetchPage(ctx context.Context) bool {
var res *readDataResult
var err error
for {
res, err = it.pf.fetch(ctx, it.c, it.pageToken)
if err != errIncompleteJob {
break
}
}
if err != nil {
it.err = err
return false
}
it.rs = res.rows
it.nextToken = res.pageToken
return true
}
// getEnoughData loads new data into rs until offset no longer points beyond the end of rs.
func (it *Iterator) getEnoughData(ctx context.Context) bool {
if len(it.rs) == 0 {
// Either we have not yet fetched any pages, or we are iterating over an empty dataset.
// In the former case, we should fetch a page of data, so that we can depend on the resultant nextToken.
// In the latter case, it is harmless to fetch a page of data.
if !it.fetchPage(ctx) {
return false
}
}
for it.offset >= int64(len(it.rs)) {
// If offset is still outside the bounds of the loaded data,
// but there are no more pages of data to fetch, then we have
// failed to satisfy the offset.
if it.nextToken == "" {
return false
}
// offset cannot be satisfied with the currently loaded data,
// so we fetch the next page. We no longer need the existing
// cached rows, so we remove them and update the offset to be
// relative to the new page that we're about to fetch.
// NOTE: we can't just set offset to 0, because after
// marshalling/unmarshalling, it's possible for the offset to
// point arbitrarily far beyond the end of rs.
// This can happen if the server returns a different size
// results page before and after marshalling.
it.offset -= int64(len(it.rs))
it.pageToken = it.nextToken
if !it.fetchPage(ctx) {
return false
}
}
return true
}
// Next advances the Iterator to the next row, making that row available
// via the Get method.
// Next must be called before the first call to Get, and blocks until data is available.
// Next returns false when there are no more rows available, either because
// the end of the output was reached, or because there was an error (consult
// the Err method to determine which).
func (it *Iterator) Next(ctx context.Context) bool {
if it.err != nil {
return false
}
// Advance offset to where we want it to be for the next call to Get.
it.offset++
// offset may now point beyond the end of rs, so we fetch data
// until offset is within its bounds again. If there are no more
// results available, offset will be left pointing beyond the bounds
// of rs.
// At the end of this method, rs will contain at least one element
// unless the dataset we are iterating over is empty.
return it.getEnoughData(ctx)
}
// Err returns the last error encountered by Next, or nil for no error.
func (it *Iterator) Err() error {
return it.err
}
// Get loads the current row into dst, which must implement ValueLoader.
func (it *Iterator) Get(dst interface{}) error {
if it.err != nil {
return fmt.Errorf("Get called on iterator in error state: %v", it.err)
}
// If Next has been called, then offset should always index into a
// valid row in rs, as long as there is still data available.
if it.offset >= int64(len(it.rs)) || it.offset < 0 {
return errors.New("Get called without preceding successful call to Next")
}
if dst, ok := dst.(ValueLoader); ok {
return dst.Load(it.rs[it.offset])
}
return errors.New("Get called with unsupported argument type")
}
// TODO(mcgreevy): Add a method to *Iterator that returns a schema which describes the data.

View file

@ -0,0 +1,415 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"errors"
"fmt"
"reflect"
"testing"
"golang.org/x/net/context"
)
type fetchResponse struct {
result *readDataResult // The result to return.
err error // The error to return.
}
// pageFetcherStub services fetch requests by returning data from an in-memory list of values.
type pageFetcherStub struct {
fetchResponses map[string]fetchResponse
err error
}
func (pf *pageFetcherStub) fetch(ctx context.Context, c *Client, token string) (*readDataResult, error) {
call, ok := pf.fetchResponses[token]
if !ok {
pf.err = fmt.Errorf("Unexpected page token: %q", token)
}
return call.result, call.err
}
func TestIterator(t *testing.T) {
fetchFailure := errors.New("fetch failure")
testCases := []struct {
desc string
alreadyConsumed int64 // amount to advance offset before commencing reading.
fetchResponses map[string]fetchResponse
want []ValueList
wantErr error
}{
{
desc: "Iteration over single empty page",
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{},
},
},
},
want: []ValueList{},
},
{
desc: "Iteration over single page",
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
},
want: []ValueList{{1, 2}, {11, 12}},
},
{
desc: "Iteration over two pages",
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "a",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
"a": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{{101, 102}, {111, 112}},
},
},
},
want: []ValueList{{1, 2}, {11, 12}, {101, 102}, {111, 112}},
},
{
desc: "Server response includes empty page",
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "a",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
"a": {
result: &readDataResult{
pageToken: "b",
rows: [][]Value{},
},
},
"b": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{{101, 102}, {111, 112}},
},
},
},
want: []ValueList{{1, 2}, {11, 12}, {101, 102}, {111, 112}},
},
{
desc: "Fetch error",
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "a",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
"a": {
// We returns some data from this fetch, but also an error.
// So the end result should include only data from the previous fetch.
err: fetchFailure,
result: &readDataResult{
pageToken: "b",
rows: [][]Value{{101, 102}, {111, 112}},
},
},
},
want: []ValueList{{1, 2}, {11, 12}},
wantErr: fetchFailure,
},
{
desc: "Skip over a single element",
alreadyConsumed: 1,
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "a",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
"a": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{{101, 102}, {111, 112}},
},
},
},
want: []ValueList{{11, 12}, {101, 102}, {111, 112}},
},
{
desc: "Skip over an entire page",
alreadyConsumed: 2,
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "a",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
"a": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{{101, 102}, {111, 112}},
},
},
},
want: []ValueList{{101, 102}, {111, 112}},
},
{
desc: "Skip beyond start of second page",
alreadyConsumed: 3,
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "a",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
"a": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{{101, 102}, {111, 112}},
},
},
},
want: []ValueList{{111, 112}},
},
{
desc: "Skip beyond all data",
alreadyConsumed: 4,
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "a",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
"a": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{{101, 102}, {111, 112}},
},
},
},
// In this test case, Next will return false on its first call,
// so we won't even attempt to call Get.
want: []ValueList{},
},
}
for _, tc := range testCases {
pf := &pageFetcherStub{
fetchResponses: tc.fetchResponses,
}
it := newIterator(nil, pf)
it.offset += tc.alreadyConsumed
values, err := consumeIterator(it)
if err != nil {
t.Fatalf("%s: %v", tc.desc, err)
}
if (len(values) != 0 || len(tc.want) != 0) && !reflect.DeepEqual(values, tc.want) {
t.Errorf("%s: values:\ngot: %v\nwant:%v", tc.desc, values, tc.want)
}
if it.Err() != tc.wantErr {
t.Errorf("%s: iterator.Err:\ngot: %v\nwant: %v", tc.desc, it.Err(), tc.wantErr)
}
}
}
// consumeIterator reads all values from an iterator and returns them.
func consumeIterator(it *Iterator) ([]ValueList, error) {
var got []ValueList
for it.Next(context.Background()) {
var vals ValueList
if err := it.Get(&vals); err != nil {
return nil, fmt.Errorf("err calling Get: %v", err)
} else {
got = append(got, vals)
}
}
return got, nil
}
func TestGetBeforeNext(t *testing.T) {
// TODO: once mashalling/unmarshalling of iterators is implemented, do a similar test for unmarshalled iterators.
pf := &pageFetcherStub{
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
},
}
it := newIterator(nil, pf)
var vals ValueList
if err := it.Get(&vals); err == nil {
t.Errorf("Expected error calling Get before Next")
}
}
type delayedPageFetcher struct {
pageFetcherStub
delayCount int
}
func (pf *delayedPageFetcher) fetch(ctx context.Context, c *Client, token string) (*readDataResult, error) {
if pf.delayCount > 0 {
pf.delayCount--
return nil, errIncompleteJob
}
return pf.pageFetcherStub.fetch(ctx, c, token)
}
func TestIterateIncompleteJob(t *testing.T) {
want := []ValueList{{1, 2}, {11, 12}, {101, 102}, {111, 112}}
pf := pageFetcherStub{
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "a",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
"a": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{{101, 102}, {111, 112}},
},
},
},
}
dpf := &delayedPageFetcher{
pageFetcherStub: pf,
delayCount: 1,
}
it := newIterator(nil, dpf)
values, err := consumeIterator(it)
if err != nil {
t.Fatal(err)
}
if (len(values) != 0 || len(want) != 0) && !reflect.DeepEqual(values, want) {
t.Errorf("values: got:\n%v\nwant:\n%v", values, want)
}
if it.Err() != nil {
t.Fatalf("iterator.Err: got:\n%v", it.Err())
}
if dpf.delayCount != 0 {
t.Errorf("delayCount: got: %v, want: 0", dpf.delayCount)
}
}
func TestGetDuringErrorState(t *testing.T) {
pf := &pageFetcherStub{
fetchResponses: map[string]fetchResponse{
"": {err: errors.New("bang")},
},
}
it := newIterator(nil, pf)
var vals ValueList
it.Next(context.Background())
if it.Err() == nil {
t.Errorf("Expected error after calling Next")
}
if err := it.Get(&vals); err == nil {
t.Errorf("Expected error calling Get when iterator has a non-nil error.")
}
}
func TestGetAfterFinished(t *testing.T) {
testCases := []struct {
alreadyConsumed int64 // amount to advance offset before commencing reading.
fetchResponses map[string]fetchResponse
want []ValueList
}{
{
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
},
want: []ValueList{{1, 2}, {11, 12}},
},
{
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{},
},
},
},
want: []ValueList{},
},
{
alreadyConsumed: 100,
fetchResponses: map[string]fetchResponse{
"": {
result: &readDataResult{
pageToken: "",
rows: [][]Value{{1, 2}, {11, 12}},
},
},
},
want: []ValueList{},
},
}
for _, tc := range testCases {
pf := &pageFetcherStub{
fetchResponses: tc.fetchResponses,
}
it := newIterator(nil, pf)
it.offset += tc.alreadyConsumed
values, err := consumeIterator(it)
if err != nil {
t.Fatal(err)
}
if (len(values) != 0 || len(tc.want) != 0) && !reflect.DeepEqual(values, tc.want) {
t.Errorf("values: got:\n%v\nwant:\n%v", values, tc.want)
}
if it.Err() != nil {
t.Fatalf("iterator.Err: got:\n%v\nwant:\n:nil", it.Err())
}
// Try calling Get again.
var vals ValueList
if err := it.Get(&vals); err == nil {
t.Errorf("Expected error calling Get when there are no more values")
}
}
}

124
vendor/google.golang.org/cloud/bigquery/job.go generated vendored Normal file
View file

@ -0,0 +1,124 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"errors"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
// A Job represents an operation which has been submitted to BigQuery for processing.
type Job struct {
service service
projectID string
jobID string
isQuery bool
}
// 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.
func (c *Client) JobFromID(ctx context.Context, id string) (*Job, error) {
jobType, err := c.service.getJobType(ctx, c.projectID, id)
if err != nil {
return nil, err
}
return &Job{
service: c.service,
projectID: c.projectID,
jobID: id,
isQuery: jobType == queryJobType,
}, nil
}
func (j *Job) ID() string {
return j.jobID
}
// State is one of a sequence of states that a Job progresses through as it is processed.
type State int
const (
Pending State = iota
Running
Done
)
// JobStatus contains the current State of a job, and errors encountered while processing that job.
type JobStatus struct {
State State
err error
// All errors encountered during the running of the job.
// Not all Errors are fatal, so errors here do not necessarily mean that the job has completed or was unsuccessful.
Errors []*Error
}
// jobOption is an Option which modifies a bq.Job proto.
// This is used for configuring values that apply to all operations, such as setting a jobReference.
type jobOption interface {
customizeJob(job *bq.Job, projectID string)
}
type jobID string
// JobID returns an Option that sets the job ID of a BigQuery job.
// If this Option is not used, a job ID is generated automatically.
func JobID(ID string) Option {
return jobID(ID)
}
func (opt jobID) implementsOption() {}
func (opt jobID) customizeJob(job *bq.Job, projectID string) {
job.JobReference = &bq.JobReference{
JobId: string(opt),
ProjectId: projectID,
}
}
// Done reports whether the job has completed.
// After Done returns true, the Err method will return an error if the job completed unsuccesfully.
func (s *JobStatus) Done() bool {
return s.State == Done
}
// Err returns the error that caused the job to complete unsuccesfully (if any).
func (s *JobStatus) Err() error {
return s.err
}
// Status returns the current status of the job. It fails if the Status could not be determined.
func (j *Job) Status(ctx context.Context) (*JobStatus, error) {
return j.service.jobStatus(ctx, j.projectID, j.jobID)
}
func (j *Job) implementsReadSource() {}
func (j *Job) customizeReadQuery(cursor *readQueryConf) error {
// There are mulitple kinds of jobs, but only a query job is suitable for reading.
if !j.isQuery {
return errors.New("Cannot read from a non-query job")
}
cursor.projectID = j.projectID
cursor.jobID = j.jobID
return nil
}

112
vendor/google.golang.org/cloud/bigquery/load_op.go generated vendored Normal file
View file

@ -0,0 +1,112 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"fmt"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
type loadOption interface {
customizeLoad(conf *bq.JobConfigurationLoad, projectID string)
}
// DestinationSchema returns an Option that specifies the schema to use when loading data into a new table.
// A DestinationSchema Option must be supplied when loading data from Google Cloud Storage into a non-existent table.
// Caveat: DestinationSchema is not required if the data being loaded is a datastore backup.
// schema must not be nil.
func DestinationSchema(schema Schema) Option { return destSchema{Schema: schema} }
type destSchema struct {
Schema
}
func (opt destSchema) implementsOption() {}
func (opt destSchema) customizeLoad(conf *bq.JobConfigurationLoad, projectID string) {
conf.Schema = opt.asTableSchema()
}
// MaxBadRecords returns an Option that sets the maximum number of bad records that will be ignored.
// If this maximum is exceeded, the operation will be unsuccessful.
func MaxBadRecords(n int64) Option { return maxBadRecords(n) }
type maxBadRecords int64
func (opt maxBadRecords) implementsOption() {}
func (opt maxBadRecords) customizeLoad(conf *bq.JobConfigurationLoad, projectID string) {
conf.MaxBadRecords = int64(opt)
}
// AllowJaggedRows returns an Option that causes missing trailing optional columns to be tolerated in CSV data. Missing values are treated as nulls.
func AllowJaggedRows() Option { return allowJaggedRows{} }
type allowJaggedRows struct{}
func (opt allowJaggedRows) implementsOption() {}
func (opt allowJaggedRows) customizeLoad(conf *bq.JobConfigurationLoad, projectID string) {
conf.AllowJaggedRows = true
}
// AllowQuotedNewlines returns an Option that allows quoted data sections containing newlines in CSV data.
func AllowQuotedNewlines() Option { return allowQuotedNewlines{} }
type allowQuotedNewlines struct{}
func (opt allowQuotedNewlines) implementsOption() {}
func (opt allowQuotedNewlines) customizeLoad(conf *bq.JobConfigurationLoad, projectID string) {
conf.AllowQuotedNewlines = true
}
// IgnoreUnknownValues returns an Option that causes values not matching the schema to be tolerated.
// Unknown values are ignored. For CSV this ignores extra values at the end of a line.
// For JSON this ignores named values that do not match any column name.
// If this Option is not used, records containing unknown values are treated as bad records.
// The MaxBadRecords Option can be used to customize how bad records are handled.
func IgnoreUnknownValues() Option { return ignoreUnknownValues{} }
type ignoreUnknownValues struct{}
func (opt ignoreUnknownValues) implementsOption() {}
func (opt ignoreUnknownValues) customizeLoad(conf *bq.JobConfigurationLoad, projectID string) {
conf.IgnoreUnknownValues = true
}
func (c *Client) load(ctx context.Context, dst *Table, src *GCSReference, options []Option) (*Job, error) {
job, options := initJobProto(c.projectID, options)
payload := &bq.JobConfigurationLoad{}
dst.customizeLoadDst(payload, c.projectID)
src.customizeLoadSrc(payload, c.projectID)
for _, opt := range options {
o, ok := opt.(loadOption)
if !ok {
return nil, fmt.Errorf("option (%#v) not applicable to dst/src pair: dst: %T ; src: %T", opt, dst, src)
}
o.customizeLoad(payload, c.projectID)
}
job.Configuration = &bq.JobConfiguration{
Load: payload,
}
return c.service.insertJob(ctx, job, c.projectID)
}

198
vendor/google.golang.org/cloud/bigquery/load_test.go generated vendored Normal file
View file

@ -0,0 +1,198 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"reflect"
"testing"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
func defaultLoadJob() *bq.Job {
return &bq.Job{
Configuration: &bq.JobConfiguration{
Load: &bq.JobConfigurationLoad{
DestinationTable: &bq.TableReference{
ProjectId: "project-id",
DatasetId: "dataset-id",
TableId: "table-id",
},
SourceUris: []string{"uri"},
},
},
}
}
func stringFieldSchema() *FieldSchema {
return &FieldSchema{Name: "fieldname", Type: StringFieldType}
}
func nestedFieldSchema() *FieldSchema {
return &FieldSchema{
Name: "nested",
Type: RecordFieldType,
Schema: Schema{stringFieldSchema()},
}
}
func bqStringFieldSchema() *bq.TableFieldSchema {
return &bq.TableFieldSchema{
Name: "fieldname",
Type: "STRING",
}
}
func bqNestedFieldSchema() *bq.TableFieldSchema {
return &bq.TableFieldSchema{
Name: "nested",
Type: "RECORD",
Fields: []*bq.TableFieldSchema{bqStringFieldSchema()},
}
}
func TestLoad(t *testing.T) {
testCases := []struct {
dst *Table
src *GCSReference
options []Option
want *bq.Job
}{
{
dst: defaultTable,
src: defaultGCS,
want: defaultLoadJob(),
},
{
dst: defaultTable,
src: defaultGCS,
options: []Option{
MaxBadRecords(1),
AllowJaggedRows(),
AllowQuotedNewlines(),
IgnoreUnknownValues(),
},
want: func() *bq.Job {
j := defaultLoadJob()
j.Configuration.Load.MaxBadRecords = 1
j.Configuration.Load.AllowJaggedRows = true
j.Configuration.Load.AllowQuotedNewlines = true
j.Configuration.Load.IgnoreUnknownValues = true
return j
}(),
},
{
dst: &Table{
ProjectID: "project-id",
DatasetID: "dataset-id",
TableID: "table-id",
},
options: []Option{CreateNever, WriteTruncate},
src: defaultGCS,
want: func() *bq.Job {
j := defaultLoadJob()
j.Configuration.Load.CreateDisposition = "CREATE_NEVER"
j.Configuration.Load.WriteDisposition = "WRITE_TRUNCATE"
return j
}(),
},
{
dst: &Table{
ProjectID: "project-id",
DatasetID: "dataset-id",
TableID: "table-id",
},
src: defaultGCS,
options: []Option{
DestinationSchema(Schema{
stringFieldSchema(),
nestedFieldSchema(),
}),
},
want: func() *bq.Job {
j := defaultLoadJob()
j.Configuration.Load.Schema = &bq.TableSchema{
Fields: []*bq.TableFieldSchema{
bqStringFieldSchema(),
bqNestedFieldSchema(),
}}
return j
}(),
},
{
dst: defaultTable,
src: &GCSReference{
uris: []string{"uri"},
SkipLeadingRows: 1,
SourceFormat: JSON,
Encoding: UTF_8,
FieldDelimiter: "\t",
Quote: "-",
},
want: func() *bq.Job {
j := defaultLoadJob()
j.Configuration.Load.SkipLeadingRows = 1
j.Configuration.Load.SourceFormat = "NEWLINE_DELIMITED_JSON"
j.Configuration.Load.Encoding = "UTF-8"
j.Configuration.Load.FieldDelimiter = "\t"
hyphen := "-"
j.Configuration.Load.Quote = &hyphen
return j
}(),
},
{
dst: defaultTable,
src: &GCSReference{
uris: []string{"uri"},
Quote: "",
},
want: func() *bq.Job {
j := defaultLoadJob()
j.Configuration.Load.Quote = nil
return j
}(),
},
{
dst: defaultTable,
src: &GCSReference{
uris: []string{"uri"},
Quote: "",
ForceZeroQuote: true,
},
want: func() *bq.Job {
j := defaultLoadJob()
empty := ""
j.Configuration.Load.Quote = &empty
return j
}(),
},
}
for _, tc := range testCases {
s := &testService{}
c := &Client{
service: s,
}
if _, err := c.Copy(context.Background(), tc.dst, tc.src, tc.options...); err != nil {
t.Errorf("err calling load: %v", err)
continue
}
if !reflect.DeepEqual(s.Job, tc.want) {
t.Errorf("loading: got:\n%v\nwant:\n%v", s.Job, tc.want)
}
}
}

42
vendor/google.golang.org/cloud/bigquery/query.go generated vendored Normal file
View file

@ -0,0 +1,42 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import bq "google.golang.org/api/bigquery/v2"
// Query represents a query to be executed.
type Query struct {
// The query to execute. See https://cloud.google.com/bigquery/query-reference for details.
Q string
// DefaultProjectID and DefaultDatasetID specify the dataset to use for unqualified table names in the query.
// If DefaultProjectID is set, DefaultDatasetID must also be set.
DefaultProjectID string
DefaultDatasetID string
}
func (q *Query) implementsSource() {}
func (q *Query) implementsReadSource() {}
func (q *Query) customizeQuerySrc(conf *bq.JobConfigurationQuery, projectID string) {
conf.Query = q.Q
if q.DefaultProjectID != "" || q.DefaultDatasetID != "" {
conf.DefaultDataset = &bq.DatasetReference{
DatasetId: q.DefaultDatasetID,
ProjectId: q.DefaultProjectID,
}
}
}

89
vendor/google.golang.org/cloud/bigquery/query_op.go generated vendored Normal file
View file

@ -0,0 +1,89 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"fmt"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
type queryOption interface {
customizeQuery(conf *bq.JobConfigurationQuery, projectID string)
}
// DisableQueryCache returns an Option that prevents results being fetched from the query cache.
// If this Option is not used, results are fetched from the cache if they are available.
// The query cache is a best-effort cache that is flushed whenever tables in the query are modified.
// Cached results are only available when TableID is unspecified in the query's destination Table.
// For more information, see https://cloud.google.com/bigquery/querying-data#querycaching
func DisableQueryCache() Option { return disableQueryCache{} }
type disableQueryCache struct{}
func (opt disableQueryCache) implementsOption() {}
func (opt disableQueryCache) customizeQuery(conf *bq.JobConfigurationQuery, projectID string) {
f := false
conf.UseQueryCache = &f
}
// JobPriority returns an Option that causes a query to be scheduled with the specified priority.
// The default priority is InteractivePriority.
// For more information, see https://cloud.google.com/bigquery/querying-data#batchqueries
func JobPriority(priority string) Option { return jobPriority(priority) }
type jobPriority string
func (opt jobPriority) implementsOption() {}
func (opt jobPriority) customizeQuery(conf *bq.JobConfigurationQuery, projectID string) {
conf.Priority = string(opt)
}
const (
BatchPriority = "BATCH"
InteractivePriority = "INTERACTIVE"
)
// TODO(mcgreevy): support large results.
// TODO(mcgreevy): support non-flattened results.
func (c *Client) query(ctx context.Context, dst *Table, src *Query, options []Option) (*Job, error) {
job, options := initJobProto(c.projectID, options)
payload := &bq.JobConfigurationQuery{}
dst.customizeQueryDst(payload, c.projectID)
src.customizeQuerySrc(payload, c.projectID)
for _, opt := range options {
o, ok := opt.(queryOption)
if !ok {
return nil, fmt.Errorf("option (%#v) not applicable to dst/src pair: dst: %T ; src: %T", opt, dst, src)
}
o.customizeQuery(payload, c.projectID)
}
job.Configuration = &bq.JobConfiguration{
Query: payload,
}
j, err := c.service.insertJob(ctx, job, c.projectID)
if err != nil {
return nil, err
}
j.isQuery = true
return j, nil
}

118
vendor/google.golang.org/cloud/bigquery/query_test.go generated vendored Normal file
View file

@ -0,0 +1,118 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"reflect"
"testing"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
func defaultQueryJob() *bq.Job {
return &bq.Job{
Configuration: &bq.JobConfiguration{
Query: &bq.JobConfigurationQuery{
DestinationTable: &bq.TableReference{
ProjectId: "project-id",
DatasetId: "dataset-id",
TableId: "table-id",
},
Query: "query string",
DefaultDataset: &bq.DatasetReference{
ProjectId: "def-project-id",
DatasetId: "def-dataset-id",
},
},
},
}
}
func TestQuery(t *testing.T) {
testCases := []struct {
dst *Table
src *Query
options []Option
want *bq.Job
}{
{
dst: defaultTable,
src: defaultQuery,
want: defaultQueryJob(),
},
{
dst: defaultTable,
src: &Query{
Q: "query string",
},
want: func() *bq.Job {
j := defaultQueryJob()
j.Configuration.Query.DefaultDataset = nil
return j
}(),
},
{
dst: &Table{},
src: defaultQuery,
want: func() *bq.Job {
j := defaultQueryJob()
j.Configuration.Query.DestinationTable = nil
return j
}(),
},
{
dst: &Table{
ProjectID: "project-id",
DatasetID: "dataset-id",
TableID: "table-id",
},
src: defaultQuery,
options: []Option{CreateNever, WriteTruncate},
want: func() *bq.Job {
j := defaultQueryJob()
j.Configuration.Query.WriteDisposition = "WRITE_TRUNCATE"
j.Configuration.Query.CreateDisposition = "CREATE_NEVER"
return j
}(),
},
{
dst: defaultTable,
src: defaultQuery,
options: []Option{DisableQueryCache()},
want: func() *bq.Job {
j := defaultQueryJob()
f := false
j.Configuration.Query.UseQueryCache = &f
return j
}(),
},
}
for _, tc := range testCases {
s := &testService{}
c := &Client{
service: s,
}
if _, err := c.Copy(context.Background(), tc.dst, tc.src, tc.options...); err != nil {
t.Errorf("err calling query: %v", err)
continue
}
if !reflect.DeepEqual(s.Job, tc.want) {
t.Errorf("querying: got:\n%v\nwant:\n%v", s.Job, tc.want)
}
}
}

68
vendor/google.golang.org/cloud/bigquery/read_op.go generated vendored Normal file
View file

@ -0,0 +1,68 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import "golang.org/x/net/context"
// RecordsPerRequest returns a ReadOption that sets the number of records to fetch per request when streaming data from BigQuery.
func RecordsPerRequest(n int64) ReadOption { return recordsPerRequest(n) }
type recordsPerRequest int64
func (opt recordsPerRequest) customizeRead(conf *pagingConf) {
conf.recordsPerRequest = int64(opt)
conf.setRecordsPerRequest = true
}
// StartIndex returns a ReadOption that sets the zero-based index of the row to start reading from.
func StartIndex(i uint64) ReadOption { return startIndex(i) }
type startIndex uint64
func (opt startIndex) customizeRead(conf *pagingConf) {
conf.startIndex = uint64(opt)
}
func (conf *readTableConf) fetch(ctx context.Context, c *Client, token string) (*readDataResult, error) {
return c.service.readTabledata(ctx, conf, token)
}
func (c *Client) readTable(t *Table, options []ReadOption) (*Iterator, error) {
conf := &readTableConf{}
t.customizeReadSrc(conf)
for _, o := range options {
o.customizeRead(&conf.paging)
}
return newIterator(c, conf), nil
}
func (conf *readQueryConf) fetch(ctx context.Context, c *Client, token string) (*readDataResult, error) {
return c.service.readQuery(ctx, conf, token)
}
func (c *Client) readQueryResults(job *Job, options []ReadOption) (*Iterator, error) {
conf := &readQueryConf{}
if err := job.customizeReadQuery(conf); err != nil {
return nil, err
}
for _, o := range options {
o.customizeRead(&conf.paging)
}
return newIterator(c, conf), nil
}

308
vendor/google.golang.org/cloud/bigquery/read_test.go generated vendored Normal file
View file

@ -0,0 +1,308 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"errors"
"reflect"
"testing"
"golang.org/x/net/context"
)
type readTabledataArgs struct {
conf *readTableConf
tok string
}
type readQueryArgs struct {
conf *readQueryConf
tok string
}
// readServiceStub services read requests by returning data from an in-memory list of values.
type readServiceStub struct {
// values and pageTokens are used as sources of data to return in response to calls to readTabledata or readQuery.
values [][][]Value // contains pages / rows / columns.
pageTokens map[string]string // maps incoming page token to returned page token.
// arguments are recorded for later inspection.
readTabledataCalls []readTabledataArgs
readQueryCalls []readQueryArgs
service
}
func (s *readServiceStub) readValues(tok string) *readDataResult {
result := &readDataResult{
pageToken: s.pageTokens[tok],
rows: s.values[0],
}
s.values = s.values[1:]
return result
}
func (s *readServiceStub) readTabledata(ctx context.Context, conf *readTableConf, token string) (*readDataResult, error) {
s.readTabledataCalls = append(s.readTabledataCalls, readTabledataArgs{conf, token})
return s.readValues(token), nil
}
func (s *readServiceStub) readQuery(ctx context.Context, conf *readQueryConf, token string) (*readDataResult, error) {
s.readQueryCalls = append(s.readQueryCalls, readQueryArgs{conf, token})
return s.readValues(token), nil
}
func TestRead(t *testing.T) {
// The data for the service stub to return is populated for each test case in the testCases for loop.
service := &readServiceStub{}
c := &Client{
service: service,
}
queryJob := &Job{
projectID: "project-id",
jobID: "job-id",
service: service,
isQuery: true,
}
for _, src := range []ReadSource{defaultTable, queryJob} {
testCases := []struct {
data [][][]Value
pageTokens map[string]string
want []ValueList
}{
{
data: [][][]Value{{{1, 2}, {11, 12}}, {{30, 40}, {31, 41}}},
pageTokens: map[string]string{"": "a", "a": ""},
want: []ValueList{{1, 2}, {11, 12}, {30, 40}, {31, 41}},
},
{
data: [][][]Value{{{1, 2}, {11, 12}}, {{30, 40}, {31, 41}}},
pageTokens: map[string]string{"": ""}, // no more pages after first one.
want: []ValueList{{1, 2}, {11, 12}},
},
}
for _, tc := range testCases {
service.values = tc.data
service.pageTokens = tc.pageTokens
if got, ok := doRead(t, c, src); ok {
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("reading: got:\n%v\nwant:\n%v", got, tc.want)
}
}
}
}
}
// doRead calls Read with a ReadSource. Get is repeatedly called on the Iterator returned by Read and the results are returned.
func doRead(t *testing.T, c *Client, src ReadSource) ([]ValueList, bool) {
it, err := c.Read(context.Background(), src)
if err != nil {
t.Errorf("err calling Read: %v", err)
return nil, false
}
var got []ValueList
for it.Next(context.Background()) {
var vals ValueList
if err := it.Get(&vals); err != nil {
t.Errorf("err calling Get: %v", err)
return nil, false
} else {
got = append(got, vals)
}
}
return got, true
}
func TestNoMoreValues(t *testing.T) {
c := &Client{
service: &readServiceStub{
values: [][][]Value{{{1, 2}, {11, 12}}},
},
}
it, err := c.Read(context.Background(), defaultTable)
if err != nil {
t.Fatalf("err calling Read: %v", err)
}
var vals ValueList
// We expect to retrieve two values and then fail on the next attempt.
if !it.Next(context.Background()) {
t.Fatalf("Next: got: false: want: true")
}
if !it.Next(context.Background()) {
t.Fatalf("Next: got: false: want: true")
}
if err := it.Get(&vals); err != nil {
t.Fatalf("Get: got: %v: want: nil", err)
}
if it.Next(context.Background()) {
t.Fatalf("Next: got: true: want: false")
}
if err := it.Get(&vals); err == nil {
t.Fatalf("Get: got: %v: want: non-nil", err)
}
}
// delayedReadStub simulates reading results from a query that has not yet
// completed. Its readQuery method initially reports that the query job is not
// yet complete. Subsequently, it proxies the request through to another
// service stub.
type delayedReadStub struct {
numDelays int
readServiceStub
}
func (s *delayedReadStub) readQuery(ctx context.Context, conf *readQueryConf, token string) (*readDataResult, error) {
if s.numDelays > 0 {
s.numDelays--
return nil, errIncompleteJob
}
return s.readServiceStub.readQuery(ctx, conf, token)
}
// TestIncompleteJob tests that an Iterator which reads from a query job will block until the job is complete.
func TestIncompleteJob(t *testing.T) {
service := &delayedReadStub{
numDelays: 2,
readServiceStub: readServiceStub{
values: [][][]Value{{{1, 2}}},
},
}
c := &Client{service: service}
queryJob := &Job{
projectID: "project-id",
jobID: "job-id",
service: service,
isQuery: true,
}
it, err := c.Read(context.Background(), queryJob)
if err != nil {
t.Fatalf("err calling Read: %v", err)
}
var got ValueList
want := ValueList{1, 2}
if !it.Next(context.Background()) {
t.Fatalf("Next: got: false: want: true")
}
if err := it.Get(&got); err != nil {
t.Fatalf("Error calling Get: %v", err)
}
if service.numDelays != 0 {
t.Errorf("remaining numDelays : got: %v want:0", service.numDelays)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("reading: got:\n%v\nwant:\n%v", got, want)
}
}
type errorReadService struct {
service
}
func (s *errorReadService) readTabledata(ctx context.Context, conf *readTableConf, token string) (*readDataResult, error) {
return nil, errors.New("bang!")
}
func TestReadError(t *testing.T) {
// test that service read errors are propagated back to the caller.
c := &Client{service: &errorReadService{}}
it, err := c.Read(context.Background(), defaultTable)
if err != nil {
// Read should not return an error; only Err should.
t.Fatalf("err calling Read: %v", err)
}
if it.Next(context.Background()) {
t.Fatalf("Next: got: true: want: false")
}
if err := it.Err(); err.Error() != "bang!" {
t.Fatalf("Get: got: %v: want: bang!", err)
}
}
func TestReadTabledataOptions(t *testing.T) {
// test that read options are propagated.
s := &readServiceStub{
values: [][][]Value{{{1, 2}}},
}
c := &Client{service: s}
it, err := c.Read(context.Background(), defaultTable, RecordsPerRequest(5))
if err != nil {
t.Fatalf("err calling Read: %v", err)
}
if !it.Next(context.Background()) {
t.Fatalf("Next: got: false: want: true")
}
want := []readTabledataArgs{{
conf: &readTableConf{
projectID: "project-id",
datasetID: "dataset-id",
tableID: "table-id",
paging: pagingConf{
recordsPerRequest: 5,
setRecordsPerRequest: true,
},
},
tok: "",
}}
if !reflect.DeepEqual(s.readTabledataCalls, want) {
t.Errorf("reading: got:\n%v\nwant:\n%v", s.readTabledataCalls, want)
}
}
func TestReadQueryOptions(t *testing.T) {
// test that read options are propagated.
s := &readServiceStub{
values: [][][]Value{{{1, 2}}},
}
c := &Client{service: s}
queryJob := &Job{
projectID: "project-id",
jobID: "job-id",
service: s,
isQuery: true,
}
it, err := c.Read(context.Background(), queryJob, RecordsPerRequest(5))
if err != nil {
t.Fatalf("err calling Read: %v", err)
}
if !it.Next(context.Background()) {
t.Fatalf("Next: got: false: want: true")
}
want := []readQueryArgs{{
conf: &readQueryConf{
projectID: "project-id",
jobID: "job-id",
paging: pagingConf{
recordsPerRequest: 5,
setRecordsPerRequest: true,
},
},
tok: "",
}}
if !reflect.DeepEqual(s.readQueryCalls, want) {
t.Errorf("reading: got:\n%v\nwant:\n%v", s.readQueryCalls, want)
}
}

106
vendor/google.golang.org/cloud/bigquery/schema.go generated vendored Normal file
View file

@ -0,0 +1,106 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import bq "google.golang.org/api/bigquery/v2"
// Schema describes the fields in a table or query result.
type Schema []*FieldSchema
// TODO(mcgreevy): add a function to generate a schema from a struct.
type FieldSchema struct {
// The field name.
// Must contain only letters (a-z, A-Z), numbers (0-9), or underscores (_),
// and must start with a letter or underscore.
// The maximum length is 128 characters.
Name string
// A description of the field. The maximum length is 16,384 characters.
Description string
// Whether the field may contain multiple values.
Repeated bool
// Whether the field is required. Ignored if Repeated is true.
Required bool
// The field data type. If Type is Record, then this field contains a nested schema,
// which is described by Schema.
Type FieldType
// Describes the nested schema if Type is set to Record.
Schema Schema
}
func (fs *FieldSchema) asTableFieldSchema() *bq.TableFieldSchema {
tfs := &bq.TableFieldSchema{
Description: fs.Description,
Name: fs.Name,
Type: string(fs.Type),
}
if fs.Repeated {
tfs.Mode = "REPEATED"
} else if fs.Required {
tfs.Mode = "REQUIRED"
} // else leave as default, which is interpreted as NULLABLE.
for _, f := range fs.Schema {
tfs.Fields = append(tfs.Fields, f.asTableFieldSchema())
}
return tfs
}
func (s Schema) asTableSchema() *bq.TableSchema {
var fields []*bq.TableFieldSchema
for _, f := range s {
fields = append(fields, f.asTableFieldSchema())
}
return &bq.TableSchema{Fields: fields}
}
func convertTableFieldSchema(tfs *bq.TableFieldSchema) *FieldSchema {
fs := &FieldSchema{
Description: tfs.Description,
Name: tfs.Name,
Repeated: tfs.Mode == "REPEATED",
Required: tfs.Mode == "REQUIRED",
Type: FieldType(tfs.Type),
}
for _, f := range tfs.Fields {
fs.Schema = append(fs.Schema, convertTableFieldSchema(f))
}
return fs
}
func convertTableSchema(ts *bq.TableSchema) Schema {
var s Schema
for _, f := range ts.Fields {
s = append(s, convertTableFieldSchema(f))
}
return s
}
type FieldType string
const (
StringFieldType FieldType = "STRING"
IntegerFieldType FieldType = "INTEGER"
FloatFieldType FieldType = "FLOAT"
BooleanFieldType FieldType = "BOOLEAN"
TimestampFieldType FieldType = "TIMESTAMP"
RecordFieldType FieldType = "RECORD"
)

168
vendor/google.golang.org/cloud/bigquery/schema_test.go generated vendored Normal file
View file

@ -0,0 +1,168 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"reflect"
"testing"
bq "google.golang.org/api/bigquery/v2"
)
func bqTableFieldSchema(desc, name, typ, mode string) *bq.TableFieldSchema {
return &bq.TableFieldSchema{
Description: desc,
Name: name,
Mode: mode,
Type: typ,
}
}
func fieldSchema(desc, name, typ string, repeated, required bool) *FieldSchema {
return &FieldSchema{
Description: desc,
Name: name,
Repeated: repeated,
Required: required,
Type: FieldType(typ),
}
}
func TestSchemaConversion(t *testing.T) {
testCases := []struct {
schema Schema
bqSchema *bq.TableSchema
}{
{
// required
bqSchema: &bq.TableSchema{
Fields: []*bq.TableFieldSchema{
bqTableFieldSchema("desc", "name", "STRING", "REQUIRED"),
},
},
schema: Schema{
fieldSchema("desc", "name", "STRING", false, true),
},
},
{
// repeated
bqSchema: &bq.TableSchema{
Fields: []*bq.TableFieldSchema{
bqTableFieldSchema("desc", "name", "STRING", "REPEATED"),
},
},
schema: Schema{
fieldSchema("desc", "name", "STRING", true, false),
},
},
{
// nullable, string
bqSchema: &bq.TableSchema{
Fields: []*bq.TableFieldSchema{
bqTableFieldSchema("desc", "name", "STRING", ""),
},
},
schema: Schema{
fieldSchema("desc", "name", "STRING", false, false),
},
},
{
// integer
bqSchema: &bq.TableSchema{
Fields: []*bq.TableFieldSchema{
bqTableFieldSchema("desc", "name", "INTEGER", ""),
},
},
schema: Schema{
fieldSchema("desc", "name", "INTEGER", false, false),
},
},
{
// float
bqSchema: &bq.TableSchema{
Fields: []*bq.TableFieldSchema{
bqTableFieldSchema("desc", "name", "FLOAT", ""),
},
},
schema: Schema{
fieldSchema("desc", "name", "FLOAT", false, false),
},
},
{
// boolean
bqSchema: &bq.TableSchema{
Fields: []*bq.TableFieldSchema{
bqTableFieldSchema("desc", "name", "BOOLEAN", ""),
},
},
schema: Schema{
fieldSchema("desc", "name", "BOOLEAN", false, false),
},
},
{
// timestamp
bqSchema: &bq.TableSchema{
Fields: []*bq.TableFieldSchema{
bqTableFieldSchema("desc", "name", "TIMESTAMP", ""),
},
},
schema: Schema{
fieldSchema("desc", "name", "TIMESTAMP", false, false),
},
},
{
// nested
bqSchema: &bq.TableSchema{
Fields: []*bq.TableFieldSchema{
&bq.TableFieldSchema{
Description: "An outer schema wrapping a nested schema",
Name: "outer",
Mode: "REQUIRED",
Type: "RECORD",
Fields: []*bq.TableFieldSchema{
bqTableFieldSchema("inner field", "inner", "STRING", ""),
},
},
},
},
schema: Schema{
&FieldSchema{
Description: "An outer schema wrapping a nested schema",
Name: "outer",
Required: true,
Type: "RECORD",
Schema: []*FieldSchema{
&FieldSchema{
Description: "inner field",
Name: "inner",
Type: "STRING",
},
},
},
},
},
}
for _, tc := range testCases {
bqSchema := tc.schema.asTableSchema()
if !reflect.DeepEqual(bqSchema, tc.bqSchema) {
t.Errorf("converting to TableSchema: got:\n%v\nwant:\n%v", bqSchema, tc.bqSchema)
}
schema := convertTableSchema(tc.bqSchema)
if !reflect.DeepEqual(schema, tc.schema) {
t.Errorf("converting to Schema: got:\n%v\nwant:\n%v", schema, tc.schema)
}
}
}

403
vendor/google.golang.org/cloud/bigquery/service.go generated vendored Normal file
View file

@ -0,0 +1,403 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"errors"
"fmt"
"net/http"
"sync"
"time"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
// service provides an internal abstraction to isolate the generated
// BigQuery API; most of this package uses this interface instead.
// The single implementation, *bigqueryService, contains all the knowledge
// of the generated BigQuery API.
type service interface {
// Jobs
insertJob(ctx context.Context, job *bq.Job, projectId string) (*Job, error)
getJobType(ctx context.Context, projectId, jobID string) (jobType, error)
jobStatus(ctx context.Context, projectId, jobID string) (*JobStatus, error)
// Queries
// readQuery reads data resulting from a query job. If the job is not
// yet complete, an errIncompleteJob is returned. readQuery may be
// called repeatedly to wait for results indefinitely.
readQuery(ctx context.Context, conf *readQueryConf, pageToken string) (*readDataResult, error)
readTabledata(ctx context.Context, conf *readTableConf, pageToken string) (*readDataResult, error)
// Tables
createTable(ctx context.Context, conf *createTableConf) error
getTableMetadata(ctx context.Context, projectID, datasetID, tableID string) (*TableMetadata, error)
deleteTable(ctx context.Context, projectID, datasetID, tableID string) error
listTables(ctx context.Context, projectID, datasetID, pageToken string) ([]*Table, string, error)
patchTable(ctx context.Context, projectID, datasetID, tableID string, conf *patchTableConf) (*TableMetadata, error)
}
type bigqueryService struct {
s *bq.Service
}
func newBigqueryService(client *http.Client) (*bigqueryService, error) {
s, err := bq.New(client)
if err != nil {
return nil, fmt.Errorf("constructing bigquery client: %v", err)
}
return &bigqueryService{s: s}, nil
}
// getPages calls the supplied getPage function repeatedly until there are no pages left to get.
// token is the token of the initial page to start from. Use an empty string to start from the beginning.
func getPages(token string, getPage func(token string) (nextToken string, err error)) error {
for {
var err error
token, err = getPage(token)
if err != nil {
return err
}
if token == "" {
return nil
}
}
}
func (s *bigqueryService) insertJob(ctx context.Context, job *bq.Job, projectID string) (*Job, error) {
res, err := s.s.Jobs.Insert(projectID, job).Context(ctx).Do()
if err != nil {
return nil, err
}
return &Job{service: s, projectID: projectID, jobID: res.JobReference.JobId}, nil
}
type pagingConf struct {
recordsPerRequest int64
setRecordsPerRequest bool
startIndex uint64
}
type readTableConf struct {
projectID, datasetID, tableID string
paging pagingConf
schema Schema // lazily initialized when the first page of data is fetched.
}
type readDataResult struct {
pageToken string
rows [][]Value
totalRows uint64
schema Schema
}
type readQueryConf struct {
projectID, jobID string
paging pagingConf
}
func (s *bigqueryService) readTabledata(ctx context.Context, conf *readTableConf, pageToken string) (*readDataResult, error) {
// Prepare request to fetch one page of table data.
req := s.s.Tabledata.List(conf.projectID, conf.datasetID, conf.tableID)
if pageToken != "" {
req.PageToken(pageToken)
} else {
req.StartIndex(conf.paging.startIndex)
}
if conf.paging.setRecordsPerRequest {
req.MaxResults(conf.paging.recordsPerRequest)
}
// Fetch the table schema in the background, if necessary.
var schemaErr error
var schemaFetch sync.WaitGroup
if conf.schema == nil {
schemaFetch.Add(1)
go func() {
defer schemaFetch.Done()
var t *bq.Table
t, schemaErr = s.s.Tables.Get(conf.projectID, conf.datasetID, conf.tableID).
Fields("schema").
Context(ctx).
Do()
if schemaErr == nil && t.Schema != nil {
conf.schema = convertTableSchema(t.Schema)
}
}()
}
res, err := req.Context(ctx).Do()
if err != nil {
return nil, err
}
schemaFetch.Wait()
if schemaErr != nil {
return nil, schemaErr
}
result := &readDataResult{
pageToken: res.PageToken,
totalRows: uint64(res.TotalRows),
schema: conf.schema,
}
result.rows, err = convertRows(res.Rows, conf.schema)
if err != nil {
return nil, err
}
return result, nil
}
var errIncompleteJob = errors.New("internal error: query results not available because job is not complete")
// getQueryResultsTimeout controls the maximum duration of a request to the
// BigQuery GetQueryResults endpoint. Setting a long timeout here does not
// cause increased overall latency, as results are returned as soon as they are
// available.
const getQueryResultsTimeout = time.Minute
func (s *bigqueryService) readQuery(ctx context.Context, conf *readQueryConf, pageToken string) (*readDataResult, error) {
req := s.s.Jobs.GetQueryResults(conf.projectID, conf.jobID).
TimeoutMs(getQueryResultsTimeout.Nanoseconds() / 1e6)
if pageToken != "" {
req.PageToken(pageToken)
} else {
req.StartIndex(conf.paging.startIndex)
}
if conf.paging.setRecordsPerRequest {
req.MaxResults(conf.paging.recordsPerRequest)
}
res, err := req.Context(ctx).Do()
if err != nil {
return nil, err
}
if !res.JobComplete {
return nil, errIncompleteJob
}
schema := convertTableSchema(res.Schema)
result := &readDataResult{
pageToken: res.PageToken,
totalRows: res.TotalRows,
schema: schema,
}
result.rows, err = convertRows(res.Rows, schema)
if err != nil {
return nil, err
}
return result, nil
}
type jobType int
const (
copyJobType jobType = iota
extractJobType
loadJobType
queryJobType
)
func (s *bigqueryService) getJobType(ctx context.Context, projectID, jobID string) (jobType, error) {
res, err := s.s.Jobs.Get(projectID, jobID).
Fields("configuration").
Context(ctx).
Do()
if err != nil {
return 0, err
}
switch {
case res.Configuration.Copy != nil:
return copyJobType, nil
case res.Configuration.Extract != nil:
return extractJobType, nil
case res.Configuration.Load != nil:
return loadJobType, nil
case res.Configuration.Query != nil:
return queryJobType, nil
default:
return 0, errors.New("unknown job type")
}
}
func (s *bigqueryService) jobStatus(ctx context.Context, projectID, jobID string) (*JobStatus, error) {
res, err := s.s.Jobs.Get(projectID, jobID).
Fields("status"). // Only fetch what we need.
Context(ctx).
Do()
if err != nil {
return nil, err
}
return jobStatusFromProto(res.Status)
}
var stateMap = map[string]State{"PENDING": Pending, "RUNNING": Running, "DONE": Done}
func jobStatusFromProto(status *bq.JobStatus) (*JobStatus, error) {
state, ok := stateMap[status.State]
if !ok {
return nil, fmt.Errorf("unexpected job state: %v", status.State)
}
newStatus := &JobStatus{
State: state,
err: nil,
}
if err := errorFromErrorProto(status.ErrorResult); state == Done && err != nil {
newStatus.err = err
}
for _, ep := range status.Errors {
newStatus.Errors = append(newStatus.Errors, errorFromErrorProto(ep))
}
return newStatus, nil
}
// listTables returns a subset of tables that belong to a dataset, and a token for fetching the next subset.
func (s *bigqueryService) listTables(ctx context.Context, projectID, datasetID, pageToken string) ([]*Table, string, error) {
var tables []*Table
res, err := s.s.Tables.List(projectID, datasetID).
PageToken(pageToken).
Context(ctx).
Do()
if err != nil {
return nil, "", err
}
for _, t := range res.Tables {
tables = append(tables, convertListedTable(t))
}
return tables, res.NextPageToken, nil
}
type createTableConf struct {
projectID, datasetID, tableID string
expiration time.Time
viewQuery string
}
// createTable creates a table in the BigQuery service.
// expiration is an optional time after which the table will be deleted and its storage reclaimed.
// If viewQuery is non-empty, the created table will be of type VIEW.
// Note: expiration can only be set during table creation.
// Note: after table creation, a view can be modified only if its table was initially created with a view.
func (s *bigqueryService) createTable(ctx context.Context, conf *createTableConf) error {
table := &bq.Table{
TableReference: &bq.TableReference{
ProjectId: conf.projectID,
DatasetId: conf.datasetID,
TableId: conf.tableID,
},
}
if !conf.expiration.IsZero() {
table.ExpirationTime = conf.expiration.UnixNano() / 1000
}
if conf.viewQuery != "" {
table.View = &bq.ViewDefinition{
Query: conf.viewQuery,
}
}
_, err := s.s.Tables.Insert(conf.projectID, conf.datasetID, table).Context(ctx).Do()
return err
}
func (s *bigqueryService) getTableMetadata(ctx context.Context, projectID, datasetID, tableID string) (*TableMetadata, error) {
table, err := s.s.Tables.Get(projectID, datasetID, tableID).Context(ctx).Do()
if err != nil {
return nil, err
}
return bqTableToMetadata(table), nil
}
func (s *bigqueryService) deleteTable(ctx context.Context, projectID, datasetID, tableID string) error {
return s.s.Tables.Delete(projectID, datasetID, tableID).Context(ctx).Do()
}
func bqTableToMetadata(t *bq.Table) *TableMetadata {
md := &TableMetadata{
Description: t.Description,
Name: t.FriendlyName,
Type: TableType(t.Type),
ID: t.Id,
NumBytes: t.NumBytes,
NumRows: t.NumRows,
}
if t.ExpirationTime != 0 {
md.ExpirationTime = time.Unix(0, t.ExpirationTime*1e6)
}
if t.CreationTime != 0 {
md.CreationTime = time.Unix(0, t.CreationTime*1e6)
}
if t.LastModifiedTime != 0 {
md.LastModifiedTime = time.Unix(0, int64(t.LastModifiedTime*1e6))
}
if t.Schema != nil {
md.Schema = convertTableSchema(t.Schema)
}
if t.View != nil {
md.View = t.View.Query
}
return md
}
func convertListedTable(t *bq.TableListTables) *Table {
return &Table{
ProjectID: t.TableReference.ProjectId,
DatasetID: t.TableReference.DatasetId,
TableID: t.TableReference.TableId,
}
}
// patchTableConf contains fields to be patched.
type patchTableConf struct {
// These fields are omitted from the patch operation if nil.
Description *string
Name *string
}
func (s *bigqueryService) patchTable(ctx context.Context, projectID, datasetID, tableID string, conf *patchTableConf) (*TableMetadata, error) {
t := &bq.Table{}
forceSend := func(field string) {
t.ForceSendFields = append(t.ForceSendFields, field)
}
if conf.Description != nil {
t.Description = *conf.Description
forceSend("Description")
}
if conf.Name != nil {
t.FriendlyName = *conf.Name
forceSend("FriendlyName")
}
table, err := s.s.Tables.Patch(projectID, datasetID, tableID, t).
Context(ctx).
Do()
if err != nil {
return nil, err
}
return bqTableToMetadata(table), nil
}

278
vendor/google.golang.org/cloud/bigquery/table.go generated vendored Normal file
View file

@ -0,0 +1,278 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"fmt"
"time"
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
// A Table is a reference to a BigQuery table.
type Table struct {
// ProjectID, DatasetID and TableID may be omitted if the Table is the destination for a query.
// In this case the result will be stored in an ephemeral table.
ProjectID string
DatasetID string
// TableID must contain only letters (a-z, A-Z), numbers (0-9), or underscores (_).
// The maximum length is 1,024 characters.
TableID string
service service
}
// TableMetadata contains information about a BigQuery table.
type TableMetadata struct {
Description string // The user-friendly description of this table.
Name string // The user-friendly name for this table.
Schema Schema
View string
ID string // An opaque ID uniquely identifying the table.
Type TableType
// The time when this table expires. If not set, the table will persist
// indefinitely. Expired tables will be deleted and their storage reclaimed.
ExpirationTime time.Time
CreationTime time.Time
LastModifiedTime time.Time
// The size of the table in bytes.
// This does not include data that is being buffered during a streaming insert.
NumBytes int64
// The number of rows of data in this table.
// This does not include data that is being buffered during a streaming insert.
NumRows uint64
}
// Tables is a group of tables. The tables may belong to differing projects or datasets.
type Tables []*Table
// CreateDisposition specifies the circumstances under which destination table will be created.
// Default is CreateIfNeeded.
type TableCreateDisposition string
const (
// The table will be created if it does not already exist. Tables are created atomically on successful completion of a job.
CreateIfNeeded TableCreateDisposition = "CREATE_IF_NEEDED"
// The table must already exist and will not be automatically created.
CreateNever TableCreateDisposition = "CREATE_NEVER"
)
func CreateDisposition(disp TableCreateDisposition) Option { return disp }
func (opt TableCreateDisposition) implementsOption() {}
func (opt TableCreateDisposition) customizeLoad(conf *bq.JobConfigurationLoad, projectID string) {
conf.CreateDisposition = string(opt)
}
func (opt TableCreateDisposition) customizeCopy(conf *bq.JobConfigurationTableCopy, projectID string) {
conf.CreateDisposition = string(opt)
}
func (opt TableCreateDisposition) customizeQuery(conf *bq.JobConfigurationQuery, projectID string) {
conf.CreateDisposition = string(opt)
}
// TableWriteDisposition specifies how existing data in a destination table is treated.
// Default is WriteAppend.
type TableWriteDisposition string
const (
// Data will be appended to any existing data in the destination table.
// Data is appended atomically on successful completion of a job.
WriteAppend TableWriteDisposition = "WRITE_APPEND"
// Existing data in the destination table will be overwritten.
// Data is overwritten atomically on successful completion of a job.
WriteTruncate TableWriteDisposition = "WRITE_TRUNCATE"
// Writes will fail if the destination table already contains data.
WriteEmpty TableWriteDisposition = "WRITE_EMPTY"
)
func WriteDisposition(disp TableWriteDisposition) Option { return disp }
func (opt TableWriteDisposition) implementsOption() {}
func (opt TableWriteDisposition) customizeLoad(conf *bq.JobConfigurationLoad, projectID string) {
conf.WriteDisposition = string(opt)
}
func (opt TableWriteDisposition) customizeCopy(conf *bq.JobConfigurationTableCopy, projectID string) {
conf.WriteDisposition = string(opt)
}
func (opt TableWriteDisposition) customizeQuery(conf *bq.JobConfigurationQuery, projectID string) {
conf.WriteDisposition = string(opt)
}
// TableType is the type of table.
type TableType string
const (
RegularTable TableType = "TABLE"
ViewTable TableType = "VIEW"
)
func (t *Table) implementsSource() {}
func (t *Table) implementsReadSource() {}
func (t *Table) implementsDestination() {}
func (ts Tables) implementsSource() {}
func (t *Table) tableRefProto() *bq.TableReference {
return &bq.TableReference{
ProjectId: t.ProjectID,
DatasetId: t.DatasetID,
TableId: t.TableID,
}
}
// FullyQualifiedName returns the ID of the table in projectID:datasetID.tableID format.
func (t *Table) FullyQualifiedName() string {
return fmt.Sprintf("%s:%s.%s", t.ProjectID, t.DatasetID, t.TableID)
}
// implicitTable reports whether Table is an empty placeholder, which signifies that a new table should be created with an auto-generated Table ID.
func (t *Table) implicitTable() bool {
return t.ProjectID == "" && t.DatasetID == "" && t.TableID == ""
}
func (t *Table) customizeLoadDst(conf *bq.JobConfigurationLoad, projectID string) {
conf.DestinationTable = t.tableRefProto()
}
func (t *Table) customizeExtractSrc(conf *bq.JobConfigurationExtract, projectID string) {
conf.SourceTable = t.tableRefProto()
}
func (t *Table) customizeCopyDst(conf *bq.JobConfigurationTableCopy, projectID string) {
conf.DestinationTable = t.tableRefProto()
}
func (ts Tables) customizeCopySrc(conf *bq.JobConfigurationTableCopy, projectID string) {
for _, t := range ts {
conf.SourceTables = append(conf.SourceTables, t.tableRefProto())
}
}
func (t *Table) customizeQueryDst(conf *bq.JobConfigurationQuery, projectID string) {
if !t.implicitTable() {
conf.DestinationTable = t.tableRefProto()
}
}
func (t *Table) customizeReadSrc(cursor *readTableConf) {
cursor.projectID = t.ProjectID
cursor.datasetID = t.DatasetID
cursor.tableID = t.TableID
}
// OpenTable creates a handle to an existing BigQuery table. If the table does not already exist, subsequent uses of the *Table will fail.
func (c *Client) OpenTable(projectID, datasetID, tableID string) *Table {
return &Table{ProjectID: projectID, DatasetID: datasetID, TableID: tableID, service: c.service}
}
// CreateTable creates a table in the BigQuery service and returns a handle to it.
func (c *Client) CreateTable(ctx context.Context, projectID, datasetID, tableID string, options ...CreateTableOption) (*Table, error) {
conf := &createTableConf{
projectID: projectID,
datasetID: datasetID,
tableID: tableID,
}
for _, o := range options {
o.customizeCreateTable(conf)
}
if err := c.service.createTable(ctx, conf); err != nil {
return nil, err
}
return &Table{ProjectID: projectID, DatasetID: datasetID, TableID: tableID, service: c.service}, nil
}
// Metadata fetches the metadata for the table.
func (t *Table) Metadata(ctx context.Context) (*TableMetadata, error) {
return t.service.getTableMetadata(ctx, t.ProjectID, t.DatasetID, t.TableID)
}
// Delete deletes the table.
func (t *Table) Delete(ctx context.Context) error {
return t.service.deleteTable(ctx, t.ProjectID, t.DatasetID, t.TableID)
}
// A CreateTableOption is an optional argument to CreateTable.
type CreateTableOption interface {
customizeCreateTable(*createTableConf)
}
type tableExpiration time.Time
// TableExpiration returns a CreateTableOption which will cause the created table to be deleted after the expiration time.
func TableExpiration(exp time.Time) CreateTableOption { return tableExpiration(exp) }
func (opt tableExpiration) customizeCreateTable(conf *createTableConf) {
conf.expiration = time.Time(opt)
}
type viewQuery string
// ViewQuery returns a CreateTableOption that causes the created table to be a virtual table defined by the supplied query.
// For more information see: https://cloud.google.com/bigquery/querying-data#views
func ViewQuery(query string) CreateTableOption { return viewQuery(query) }
func (opt viewQuery) customizeCreateTable(conf *createTableConf) {
conf.viewQuery = string(opt)
}
// TableMetadataPatch represents a set of changes to a table's metadata.
type TableMetadataPatch struct {
s service
projectID, datasetID, tableID string
conf patchTableConf
}
// Patch returns a *TableMetadataPatch, which can be used to modify specific Table metadata fields.
// In order to apply the changes, the TableMetadataPatch's Apply method must be called.
func (t *Table) Patch() *TableMetadataPatch {
return &TableMetadataPatch{
s: t.service,
projectID: t.ProjectID,
datasetID: t.DatasetID,
tableID: t.TableID,
}
}
// Description sets the table description.
func (p *TableMetadataPatch) Description(desc string) {
p.conf.Description = &desc
}
// Name sets the table name.
func (p *TableMetadataPatch) Name(name string) {
p.conf.Name = &name
}
// TODO(mcgreevy): support patching the schema.
// Apply applies the patch operation.
func (p *TableMetadataPatch) Apply(ctx context.Context) (*TableMetadata, error) {
return p.s.patchTable(ctx, p.projectID, p.datasetID, p.tableID, &p.conf)
}

51
vendor/google.golang.org/cloud/bigquery/utils_test.go generated vendored Normal file
View file

@ -0,0 +1,51 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
var defaultTable = &Table{
ProjectID: "project-id",
DatasetID: "dataset-id",
TableID: "table-id",
}
var defaultGCS = &GCSReference{
uris: []string{"uri"},
}
var defaultQuery = &Query{
Q: "query string",
DefaultProjectID: "def-project-id",
DefaultDatasetID: "def-dataset-id",
}
type testService struct {
*bq.Job
service
}
func (s *testService) insertJob(ctx context.Context, job *bq.Job, projectID string) (*Job, error) {
s.Job = job
return &Job{}, nil
}
func (s *testService) jobStatus(ctx context.Context, projectID, jobID string) (*JobStatus, error) {
return &JobStatus{State: Done}, nil
}

145
vendor/google.golang.org/cloud/bigquery/value.go generated vendored Normal file
View file

@ -0,0 +1,145 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"errors"
"fmt"
"strconv"
"time"
bq "google.golang.org/api/bigquery/v2"
)
// Value stores the contents of a single cell from a BigQuery result.
type Value interface{}
// ValueLoader stores a slice of Values representing a result row from a Read operation.
// See Iterator.Get for more information.
type ValueLoader interface {
Load(v []Value) error
}
// ValueList converts a []Value to implement ValueLoader.
type ValueList []Value
// Load stores a sequence of values in a ValueList.
func (vs *ValueList) Load(v []Value) error {
*vs = append(*vs, v...)
return nil
}
// 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.
func convertRows(rows []*bq.TableRow, schema Schema) ([][]Value, error) {
var rs [][]Value
for _, r := range rows {
row, err := convertRow(r, schema)
if err != nil {
return nil, err
}
rs = append(rs, row)
}
return rs, nil
}
func convertRow(r *bq.TableRow, schema Schema) ([]Value, error) {
if len(schema) != len(r.F) {
return nil, errors.New("schema length does not match row length")
}
var values []Value
for i, cell := range r.F {
fs := schema[i]
v, err := convertValue(cell.V, fs.Type, fs.Schema)
if err != nil {
return nil, err
}
values = append(values, v)
}
return values, nil
}
func convertValue(val interface{}, typ FieldType, schema Schema) (Value, error) {
switch val := val.(type) {
case nil:
return nil, nil
case []interface{}:
return convertRepeatedRecord(val, typ, schema)
case map[string]interface{}:
return convertNestedRecord(val, schema)
case string:
return convertBasicType(val, typ)
default:
return nil, fmt.Errorf("got value %v; expected a value of type %s", val, typ)
}
}
func convertRepeatedRecord(vals []interface{}, typ FieldType, schema Schema) (Value, error) {
var values []Value
for _, cell := range vals {
// each cell contains a single entry, keyed by "v"
val := cell.(map[string]interface{})["v"]
v, err := convertValue(val, typ, schema)
if err != nil {
return nil, err
}
values = append(values, v)
}
return values, nil
}
func convertNestedRecord(val map[string]interface{}, schema Schema) (Value, error) {
// convertNestedRecord is similar to convertRow, as a record has the same structure as a row.
// Nested records are wrapped in a map with a single key, "f".
record := val["f"].([]interface{})
if len(record) != len(schema) {
return nil, errors.New("schema length does not match record length")
}
var values []Value
for i, cell := range record {
// each cell contains a single entry, keyed by "v"
val := cell.(map[string]interface{})["v"]
fs := schema[i]
v, err := convertValue(val, fs.Type, fs.Schema)
if err != nil {
return nil, err
}
values = append(values, v)
}
return values, nil
}
// convertBasicType returns val as an interface with a concrete type specified by typ.
func convertBasicType(val string, typ FieldType) (Value, error) {
switch typ {
case StringFieldType:
return val, nil
case IntegerFieldType:
return strconv.Atoi(val)
case FloatFieldType:
return strconv.ParseFloat(val, 64)
case BooleanFieldType:
return strconv.ParseBool(val)
case TimestampFieldType:
f, err := strconv.ParseFloat(val, 64)
return Value(time.Unix(0, int64(f*1e9))), err
default:
return nil, errors.New("unrecognized type")
}
}

325
vendor/google.golang.org/cloud/bigquery/value_test.go generated vendored Normal file
View file

@ -0,0 +1,325 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bigquery
import (
"fmt"
"reflect"
"testing"
"time"
bq "google.golang.org/api/bigquery/v2"
)
func TestConvertBasicValues(t *testing.T) {
schema := []*FieldSchema{
{Type: StringFieldType},
{Type: IntegerFieldType},
{Type: FloatFieldType},
{Type: BooleanFieldType},
}
row := &bq.TableRow{
F: []*bq.TableCell{
{V: "a"},
{V: "1"},
{V: "1.2"},
{V: "true"},
},
}
got, err := convertRow(row, schema)
if err != nil {
t.Fatalf("error converting: %v", err)
}
want := []Value{"a", 1, 1.2, true}
if !reflect.DeepEqual(got, want) {
t.Errorf("converting basic values: got:\n%v\nwant:\n%v", got, want)
}
}
func TestConvertTime(t *testing.T) {
schema := []*FieldSchema{
{Type: TimestampFieldType},
}
thyme := time.Date(1970, 1, 1, 10, 0, 0, 10, time.UTC)
row := &bq.TableRow{
F: []*bq.TableCell{
{V: fmt.Sprintf("%.10f", float64(thyme.UnixNano())/1e9)},
},
}
got, err := convertRow(row, schema)
if err != nil {
t.Fatalf("error converting: %v", err)
}
if !got[0].(time.Time).Equal(thyme) {
t.Errorf("converting basic values: got:\n%v\nwant:\n%v", got, thyme)
}
}
func TestConvertNullValues(t *testing.T) {
schema := []*FieldSchema{
{Type: StringFieldType},
}
row := &bq.TableRow{
F: []*bq.TableCell{
{V: nil},
},
}
got, err := convertRow(row, schema)
if err != nil {
t.Fatalf("error converting: %v", err)
}
want := []Value{nil}
if !reflect.DeepEqual(got, want) {
t.Errorf("converting null values: got:\n%v\nwant:\n%v", got, want)
}
}
func TestBasicRepetition(t *testing.T) {
schema := []*FieldSchema{
{Type: IntegerFieldType, Repeated: true},
}
row := &bq.TableRow{
F: []*bq.TableCell{
{
V: []interface{}{
map[string]interface{}{
"v": "1",
},
map[string]interface{}{
"v": "2",
},
map[string]interface{}{
"v": "3",
},
},
},
},
}
got, err := convertRow(row, schema)
if err != nil {
t.Fatalf("error converting: %v", err)
}
want := []Value{[]Value{1, 2, 3}}
if !reflect.DeepEqual(got, want) {
t.Errorf("converting basic repeated values: got:\n%v\nwant:\n%v", got, want)
}
}
func TestNestedRecordContainingRepetition(t *testing.T) {
schema := []*FieldSchema{
{
Type: RecordFieldType,
Schema: Schema{
{Type: IntegerFieldType, Repeated: true},
},
},
}
row := &bq.TableRow{
F: []*bq.TableCell{
{
V: map[string]interface{}{
"f": []interface{}{
map[string]interface{}{
"v": []interface{}{
map[string]interface{}{"v": "1"},
map[string]interface{}{"v": "2"},
map[string]interface{}{"v": "3"},
},
},
},
},
},
},
}
got, err := convertRow(row, schema)
if err != nil {
t.Fatalf("error converting: %v", err)
}
want := []Value{[]Value{[]Value{1, 2, 3}}}
if !reflect.DeepEqual(got, want) {
t.Errorf("converting basic repeated values: got:\n%v\nwant:\n%v", got, want)
}
}
func TestRepeatedRecordContainingRepetition(t *testing.T) {
schema := []*FieldSchema{
{
Type: RecordFieldType,
Repeated: true,
Schema: Schema{
{Type: IntegerFieldType, Repeated: true},
},
},
}
row := &bq.TableRow{F: []*bq.TableCell{
{
V: []interface{}{ // repeated records.
map[string]interface{}{ // first record.
"v": map[string]interface{}{ // pointless single-key-map wrapper.
"f": []interface{}{ // list of record fields.
map[string]interface{}{ // only record (repeated ints)
"v": []interface{}{ // pointless wrapper.
map[string]interface{}{
"v": "1",
},
map[string]interface{}{
"v": "2",
},
map[string]interface{}{
"v": "3",
},
},
},
},
},
},
map[string]interface{}{ // second record.
"v": map[string]interface{}{
"f": []interface{}{
map[string]interface{}{
"v": []interface{}{
map[string]interface{}{
"v": "4",
},
map[string]interface{}{
"v": "5",
},
map[string]interface{}{
"v": "6",
},
},
},
},
},
},
},
},
}}
got, err := convertRow(row, schema)
if err != nil {
t.Fatalf("error converting: %v", err)
}
want := []Value{ // the row is a list of length 1, containing an entry for the repeated record.
[]Value{ // the repeated record is a list of length 2, containing an entry for each repetition.
[]Value{ // the record is a list of length 1, containing an entry for the repeated integer field.
[]Value{1, 2, 3}, // the repeated integer field is a list of length 3.
},
[]Value{ // second record
[]Value{4, 5, 6},
},
},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("converting repeated records with repeated values: got:\n%v\nwant:\n%v", got, want)
}
}
func TestRepeatedRecordContainingRecord(t *testing.T) {
schema := []*FieldSchema{
{
Type: RecordFieldType,
Repeated: true,
Schema: Schema{
{
Type: StringFieldType,
},
{
Type: RecordFieldType,
Schema: Schema{
{Type: IntegerFieldType},
{Type: StringFieldType},
},
},
},
},
}
row := &bq.TableRow{F: []*bq.TableCell{
{
V: []interface{}{ // repeated records.
map[string]interface{}{ // first record.
"v": map[string]interface{}{ // pointless single-key-map wrapper.
"f": []interface{}{ // list of record fields.
map[string]interface{}{ // first record field (name)
"v": "first repeated record",
},
map[string]interface{}{ // second record field (nested record).
"v": map[string]interface{}{ // pointless single-key-map wrapper.
"f": []interface{}{ // nested record fields
map[string]interface{}{
"v": "1",
},
map[string]interface{}{
"v": "two",
},
},
},
},
},
},
},
map[string]interface{}{ // second record.
"v": map[string]interface{}{
"f": []interface{}{
map[string]interface{}{
"v": "second repeated record",
},
map[string]interface{}{
"v": map[string]interface{}{
"f": []interface{}{
map[string]interface{}{
"v": "3",
},
map[string]interface{}{
"v": "four",
},
},
},
},
},
},
},
},
},
}}
got, err := convertRow(row, schema)
if err != nil {
t.Fatalf("error converting: %v", err)
}
// TODO: test with flattenresults.
want := []Value{ // the row is a list of length 1, containing an entry for the repeated record.
[]Value{ // the repeated record is a list of length 2, containing an entry for each repetition.
[]Value{ // record contains a string followed by a nested record.
"first repeated record",
[]Value{
1,
"two",
},
},
[]Value{ // second record.
"second repeated record",
[]Value{
3,
"four",
},
},
},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("converting repeated records containing record : got:\n%v\nwant:\n%v", got, want)
}
}

267
vendor/google.golang.org/cloud/bigtable/admin.go generated vendored Normal file
View file

@ -0,0 +1,267 @@
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bigtable
import (
"fmt"
"regexp"
"strings"
"golang.org/x/net/context"
"google.golang.org/cloud"
btcspb "google.golang.org/cloud/bigtable/internal/cluster_service_proto"
bttspb "google.golang.org/cloud/bigtable/internal/table_service_proto"
"google.golang.org/cloud/internal/transport"
"google.golang.org/grpc"
)
const adminAddr = "bigtabletableadmin.googleapis.com:443"
// AdminClient is a client type for performing admin operations within a specific cluster.
type AdminClient struct {
conn *grpc.ClientConn
tClient bttspb.BigtableTableServiceClient
project, zone, cluster string
}
// NewAdminClient creates a new AdminClient for a given project, zone and cluster.
func NewAdminClient(ctx context.Context, project, zone, cluster string, opts ...cloud.ClientOption) (*AdminClient, error) {
o := []cloud.ClientOption{
cloud.WithEndpoint(adminAddr),
cloud.WithScopes(AdminScope),
cloud.WithUserAgent(clientUserAgent),
}
o = append(o, opts...)
conn, err := transport.DialGRPC(ctx, o...)
if err != nil {
return nil, fmt.Errorf("dialing: %v", err)
}
return &AdminClient{
conn: conn,
tClient: bttspb.NewBigtableTableServiceClient(conn),
project: project,
zone: zone,
cluster: cluster,
}, nil
}
// Close closes the AdminClient.
func (ac *AdminClient) Close() {
ac.conn.Close()
}
func (ac *AdminClient) clusterPrefix() string {
return fmt.Sprintf("projects/%s/zones/%s/clusters/%s", ac.project, ac.zone, ac.cluster)
}
// Tables returns a list of the tables in the cluster.
func (ac *AdminClient) Tables(ctx context.Context) ([]string, error) {
prefix := ac.clusterPrefix()
req := &bttspb.ListTablesRequest{
Name: prefix,
}
res, err := ac.tClient.ListTables(ctx, req)
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/"))
}
return names, nil
}
// CreateTable creates a new table in the cluster.
// This method may return before the table's creation is complete.
func (ac *AdminClient) CreateTable(ctx context.Context, table string) error {
prefix := ac.clusterPrefix()
req := &bttspb.CreateTableRequest{
Name: prefix,
TableId: table,
}
_, err := ac.tClient.CreateTable(ctx, req)
if err != nil {
return err
}
return nil
}
// CreateColumnFamily creates a new column family in a table.
func (ac *AdminClient) CreateColumnFamily(ctx context.Context, table, family string) error {
// TODO(dsymonds): Permit specifying gcexpr and any other family settings.
prefix := ac.clusterPrefix()
req := &bttspb.CreateColumnFamilyRequest{
Name: prefix + "/tables/" + table,
ColumnFamilyId: family,
}
_, err := ac.tClient.CreateColumnFamily(ctx, req)
return err
}
// DeleteTable deletes a table and all of its data.
func (ac *AdminClient) DeleteTable(ctx context.Context, table string) error {
prefix := ac.clusterPrefix()
req := &bttspb.DeleteTableRequest{
Name: prefix + "/tables/" + table,
}
_, err := ac.tClient.DeleteTable(ctx, req)
return err
}
// DeleteColumnFamily deletes a column family in a table and all of its data.
func (ac *AdminClient) DeleteColumnFamily(ctx context.Context, table, family string) error {
prefix := ac.clusterPrefix()
req := &bttspb.DeleteColumnFamilyRequest{
Name: prefix + "/tables/" + table + "/columnFamilies/" + family,
}
_, err := ac.tClient.DeleteColumnFamily(ctx, req)
return err
}
// TableInfo represents information about a table.
type TableInfo struct {
Families []string
}
// TableInfo retrieves information about a table.
func (ac *AdminClient) TableInfo(ctx context.Context, table string) (*TableInfo, error) {
prefix := ac.clusterPrefix()
req := &bttspb.GetTableRequest{
Name: prefix + "/tables/" + table,
}
res, err := ac.tClient.GetTable(ctx, req)
if err != nil {
return nil, err
}
ti := &TableInfo{}
for fam := range res.ColumnFamilies {
ti.Families = append(ti.Families, fam)
}
return ti, nil
}
// SetGCPolicy specifies which cells in a column family should be garbage collected.
// GC executes opportunistically in the background; table reads may return data
// matching the GC policy.
func (ac *AdminClient) SetGCPolicy(ctx context.Context, table, family string, policy GCPolicy) error {
prefix := ac.clusterPrefix()
tbl, err := ac.tClient.GetTable(ctx, &bttspb.GetTableRequest{
Name: prefix + "/tables/" + table,
})
if err != nil {
return err
}
fam, ok := tbl.ColumnFamilies[family]
if !ok {
return fmt.Errorf("unknown column family %q", family)
}
fam.GcRule = policy.proto()
_, err = ac.tClient.UpdateColumnFamily(ctx, fam)
return err
}
const clusterAdminAddr = "bigtableclusteradmin.googleapis.com:443"
// ClusterAdminClient is a client type for performing admin operations on clusters.
// These operations can be substantially more dangerous than those provided by AdminClient.
type ClusterAdminClient struct {
conn *grpc.ClientConn
cClient btcspb.BigtableClusterServiceClient
project string
}
// NewClusterAdminClient creates a new ClusterAdminClient for a given project.
func NewClusterAdminClient(ctx context.Context, project string, opts ...cloud.ClientOption) (*ClusterAdminClient, error) {
o := []cloud.ClientOption{
cloud.WithEndpoint(clusterAdminAddr),
cloud.WithScopes(ClusterAdminScope),
cloud.WithUserAgent(clientUserAgent),
}
o = append(o, opts...)
conn, err := transport.DialGRPC(ctx, o...)
if err != nil {
return nil, fmt.Errorf("dialing: %v", err)
}
return &ClusterAdminClient{
conn: conn,
cClient: btcspb.NewBigtableClusterServiceClient(conn),
project: project,
}, nil
}
// Close closes the ClusterAdminClient.
func (cac *ClusterAdminClient) Close() {
cac.conn.Close()
}
// ClusterInfo represents information about a cluster.
type ClusterInfo struct {
Name string // name of the cluster
Zone string // GCP zone of the cluster (e.g. "us-central1-a")
DisplayName string // display name for UIs
ServeNodes int // number of allocated serve nodes
}
var clusterNameRegexp = regexp.MustCompile(`^projects/([^/]+)/zones/([^/]+)/clusters/([a-z][-a-z0-9]*)$`)
// Clusters returns a list of clusters in the project.
func (cac *ClusterAdminClient) Clusters(ctx context.Context) ([]*ClusterInfo, error) {
req := &btcspb.ListClustersRequest{
Name: "projects/" + cac.project,
}
res, err := cac.cClient.ListClusters(ctx, req)
if err != nil {
return nil, err
}
// TODO(dsymonds): Deal with failed_zones.
var cis []*ClusterInfo
for _, c := range res.Clusters {
m := clusterNameRegexp.FindStringSubmatch(c.Name)
if m == nil {
return nil, fmt.Errorf("malformed cluster name %q", c.Name)
}
cis = append(cis, &ClusterInfo{
Name: m[3],
Zone: m[2],
DisplayName: c.DisplayName,
ServeNodes: int(c.ServeNodes),
})
}
return cis, nil
}
/* TODO(dsymonds): Re-enable when there's a ClusterAdmin API.
// SetClusterSize sets the number of server nodes for this cluster.
func (ac *AdminClient) SetClusterSize(ctx context.Context, nodes int) error {
req := &btcspb.GetClusterRequest{
Name: ac.clusterPrefix(),
}
clu, err := ac.cClient.GetCluster(ctx, req)
if err != nil {
return err
}
clu.ServeNodes = int32(nodes)
_, err = ac.cClient.UpdateCluster(ctx, clu)
return err
}
*/

59
vendor/google.golang.org/cloud/bigtable/admin_test.go generated vendored Normal file
View file

@ -0,0 +1,59 @@
package bigtable
import (
"reflect"
"sort"
"testing"
"time"
"golang.org/x/net/context"
"google.golang.org/cloud"
"google.golang.org/cloud/bigtable/bttest"
"google.golang.org/grpc"
)
func TestAdminIntegration(t *testing.T) {
srv, err := bttest.NewServer()
if err != nil {
t.Fatal(err)
}
defer srv.Close()
t.Logf("bttest.Server running on %s", srv.Addr)
ctx, _ := context.WithTimeout(context.Background(), 2*time.Second)
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
if err != nil {
t.Fatalf("grpc.Dial: %v", err)
}
adminClient, err := NewAdminClient(ctx, "proj", "zone", "cluster", cloud.WithBaseGRPC(conn))
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
}
if err := adminClient.CreateTable(ctx, "mytable"); err != nil {
t.Fatalf("Creating table: %v", err)
}
if err := adminClient.CreateTable(ctx, "myothertable"); err != nil {
t.Fatalf("Creating table: %v", err)
}
if got, want := list(), []string{"myothertable", "mytable"}; !reflect.DeepEqual(got, want) {
t.Errorf("adminClient.Tables returned %#v, want %#v", got, want)
}
if err := adminClient.DeleteTable(ctx, "myothertable"); err != nil {
t.Fatalf("Deleting table: %v", err)
}
if got, want := list(), []string{"mytable"}; !reflect.DeepEqual(got, want) {
t.Errorf("adminClient.Tables returned %#v, want %#v", got, want)
}
}

529
vendor/google.golang.org/cloud/bigtable/bigtable.go generated vendored Normal file
View file

@ -0,0 +1,529 @@
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bigtable // import "google.golang.org/cloud/bigtable"
import (
"fmt"
"io"
"strconv"
"time"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"google.golang.org/cloud"
btdpb "google.golang.org/cloud/bigtable/internal/data_proto"
btspb "google.golang.org/cloud/bigtable/internal/service_proto"
"google.golang.org/cloud/internal/transport"
"google.golang.org/grpc"
)
const prodAddr = "bigtable.googleapis.com:443"
// Client is a client for reading and writing data to tables in a cluster.
type Client struct {
conn *grpc.ClientConn
client btspb.BigtableServiceClient
project, zone, cluster string
}
// NewClient creates a new Client for a given project, zone and cluster.
func NewClient(ctx context.Context, project, zone, cluster string, opts ...cloud.ClientOption) (*Client, error) {
o := []cloud.ClientOption{
cloud.WithEndpoint(prodAddr),
cloud.WithScopes(Scope),
cloud.WithUserAgent(clientUserAgent),
}
o = append(o, opts...)
conn, err := transport.DialGRPC(ctx, o...)
if err != nil {
return nil, fmt.Errorf("dialing: %v", err)
}
return &Client{
conn: conn,
client: btspb.NewBigtableServiceClient(conn),
project: project,
zone: zone,
cluster: cluster,
}, nil
}
// Close closes the Client.
func (c *Client) Close() {
c.conn.Close()
}
func (c *Client) fullTableName(table string) string {
return fmt.Sprintf("projects/%s/zones/%s/clusters/%s/tables/%s", c.project, c.zone, c.cluster, table)
}
// A Table refers to a table.
type Table struct {
c *Client
table string
}
// Open opens a table.
func (c *Client) Open(table string) *Table {
return &Table{
c: c,
table: table,
}
}
// TODO(dsymonds): Read method that returns a sequence of ReadItems.
// ReadRows reads rows from a table. f is called for each row.
// If f returns false, the stream is shut down and ReadRows returns.
// f owns its argument, and f is called serially.
//
// By default, the yielded rows will contain all values in all cells.
// Use RowFilter to limit the cells returned.
func (t *Table) ReadRows(ctx context.Context, arg RowRange, f func(Row) bool, opts ...ReadOption) error {
req := &btspb.ReadRowsRequest{
TableName: t.c.fullTableName(t.table),
Target: &btspb.ReadRowsRequest_RowRange{arg.proto()},
}
for _, opt := range opts {
opt.set(req)
}
ctx, cancel := context.WithCancel(ctx) // for aborting the stream
stream, err := t.c.client.ReadRows(ctx, req)
if err != nil {
return err
}
cr := new(chunkReader)
for {
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
if row := cr.process(res); row != nil {
if !f(row) {
// Cancel and drain stream.
cancel()
for {
if _, err := stream.Recv(); err != nil {
return nil
}
}
}
}
}
return nil
}
// ReadRow is a convenience implementation of a single-row reader.
// A missing row will return a zero-length map and a nil error.
func (t *Table) ReadRow(ctx context.Context, row string, opts ...ReadOption) (Row, error) {
var r Row
err := t.ReadRows(ctx, SingleRow(row), func(rr Row) bool {
r = rr
return true
}, opts...)
return r, err
}
type chunkReader struct {
partial map[string]Row // incomplete rows
}
// process handles a single btspb.ReadRowsResponse.
// If it completes a row, that row is returned.
func (cr *chunkReader) process(rrr *btspb.ReadRowsResponse) Row {
if cr.partial == nil {
cr.partial = make(map[string]Row)
}
row := string(rrr.RowKey)
r := cr.partial[row]
if r == nil {
r = make(Row)
cr.partial[row] = r
}
for _, chunk := range rrr.Chunks {
switch c := chunk.Chunk.(type) {
case *btspb.ReadRowsResponse_Chunk_ResetRow:
r = make(Row)
cr.partial[row] = r
continue
case *btspb.ReadRowsResponse_Chunk_CommitRow:
delete(cr.partial, row)
if len(r) == 0 {
// Treat zero-content commits as absent.
continue
}
return r // assume that this is the last chunk
case *btspb.ReadRowsResponse_Chunk_RowContents:
decodeFamilyProto(r, row, c.RowContents)
}
}
return nil
}
// decodeFamilyProto adds the cell data from f to the given row.
func decodeFamilyProto(r Row, row string, f *btdpb.Family) {
fam := f.Name // does not have colon
for _, col := range f.Columns {
for _, cell := range col.Cells {
ri := ReadItem{
Row: row,
Column: fmt.Sprintf("%s:%s", fam, col.Qualifier),
Timestamp: Timestamp(cell.TimestampMicros),
Value: cell.Value,
}
r[fam] = append(r[fam], ri)
}
}
}
// A RowRange is used to describe the rows to be read.
// A RowRange is a half-open interval [Start, Limit) encompassing
// all the rows with keys at least as large as Start, and less than Limit.
// (Bigtable string comparison is the same as Go's.)
// A RowRange can be unbounded, encompassing all keys at least as large as Start.
type RowRange struct {
start string
limit string
}
// NewRange returns the new RowRange [begin, end).
func NewRange(begin, end string) RowRange {
return RowRange{
start: begin,
limit: end,
}
}
// Unbounded tests whether a RowRange is unbounded.
func (r RowRange) Unbounded() bool {
return r.limit == ""
}
// Contains says whether the RowRange contains the key.
func (r RowRange) Contains(row string) bool {
return r.start <= row && (r.limit == "" || r.limit > row)
}
// String provides a printable description of a RowRange.
func (r RowRange) String() string {
a := strconv.Quote(r.start)
if r.Unbounded() {
return fmt.Sprintf("[%s,∞)", a)
}
return fmt.Sprintf("[%s,%q)", a, r.limit)
}
func (r RowRange) proto() *btdpb.RowRange {
if r.Unbounded() {
return &btdpb.RowRange{StartKey: []byte(r.start)}
}
return &btdpb.RowRange{
StartKey: []byte(r.start),
EndKey: []byte(r.limit),
}
}
// SingleRow returns a RowRange for reading a single row.
func SingleRow(row string) RowRange {
return RowRange{
start: row,
limit: row + "\x00",
}
}
// PrefixRange returns a RowRange consisting of all keys starting with the prefix.
func PrefixRange(prefix string) RowRange {
return RowRange{
start: prefix,
limit: prefixSuccessor(prefix),
}
}
// InfiniteRange returns the RowRange consisting of all keys at least as
// large as start.
func InfiniteRange(start string) RowRange {
return RowRange{
start: start,
limit: "",
}
}
// prefixSuccessor returns the lexically smallest string greater than the
// prefix, if it exists, or "" otherwise. In either case, it is the string
// needed for the Limit of a RowRange.
func prefixSuccessor(prefix string) string {
if prefix == "" {
return "" // infinite range
}
n := len(prefix)
for n--; n >= 0 && prefix[n] == '\xff'; n-- {
}
if n == -1 {
return ""
}
ans := []byte(prefix[:n])
ans = append(ans, prefix[n]+1)
return string(ans)
}
// A ReadOption is an optional argument to ReadRows.
type ReadOption interface {
set(req *btspb.ReadRowsRequest)
}
// RowFilter returns a ReadOption that applies f to the contents of read rows.
func RowFilter(f Filter) ReadOption { return rowFilter{f} }
type rowFilter struct{ f Filter }
func (rf rowFilter) set(req *btspb.ReadRowsRequest) { req.Filter = rf.f.proto() }
// LimitRows returns a ReadOption that will limit the number of rows to be read.
func LimitRows(limit int64) ReadOption { return limitRows{limit} }
type limitRows struct{ limit int64 }
func (lr limitRows) set(req *btspb.ReadRowsRequest) { req.NumRowsLimit = lr.limit }
// A Row is returned by ReadRow. The map is keyed by column family (the prefix
// of the column name before the colon). The values are the returned ReadItems
// for that column family in the order returned by Read.
type Row map[string][]ReadItem
// Key returns the row's key, or "" if the row is empty.
func (r Row) Key() string {
for _, items := range r {
if len(items) > 0 {
return items[0].Row
}
}
return ""
}
// A ReadItem is returned by Read. A ReadItem contains data from a specific row and column.
type ReadItem struct {
Row, Column string
Timestamp Timestamp
Value []byte
}
// Apply applies a Mutation to a specific row.
func (t *Table) Apply(ctx context.Context, row string, m *Mutation, opts ...ApplyOption) error {
after := func(res proto.Message) {
for _, o := range opts {
o.after(res)
}
}
if m.cond == nil {
req := &btspb.MutateRowRequest{
TableName: t.c.fullTableName(t.table),
RowKey: []byte(row),
Mutations: m.ops,
}
res, err := t.c.client.MutateRow(ctx, req)
if err == nil {
after(res)
}
return err
}
req := &btspb.CheckAndMutateRowRequest{
TableName: t.c.fullTableName(t.table),
RowKey: []byte(row),
PredicateFilter: m.cond.proto(),
}
if m.mtrue != nil {
req.TrueMutations = m.mtrue.ops
}
if m.mfalse != nil {
req.FalseMutations = m.mfalse.ops
}
res, err := t.c.client.CheckAndMutateRow(ctx, req)
if err == nil {
after(res)
}
return err
}
// An ApplyOption is an optional argument to Apply.
type ApplyOption interface {
after(res proto.Message)
}
type applyAfterFunc func(res proto.Message)
func (a applyAfterFunc) after(res proto.Message) { a(res) }
// GetCondMutationResult returns an ApplyOption that reports whether the conditional
// mutation's condition matched.
func GetCondMutationResult(matched *bool) ApplyOption {
return applyAfterFunc(func(res proto.Message) {
if res, ok := res.(*btspb.CheckAndMutateRowResponse); ok {
*matched = res.PredicateMatched
}
})
}
// Mutation represents a set of changes for a single row of a table.
type Mutation struct {
ops []*btdpb.Mutation
// for conditional mutations
cond Filter
mtrue, mfalse *Mutation
}
// NewMutation returns a new mutation.
func NewMutation() *Mutation {
return new(Mutation)
}
// NewCondMutation returns a conditional mutation.
// The given row filter determines which mutation is applied:
// If the filter matches any cell in the row, mtrue is applied;
// otherwise, mfalse is applied.
// Either given mutation may be nil.
func NewCondMutation(cond Filter, mtrue, mfalse *Mutation) *Mutation {
return &Mutation{cond: cond, mtrue: mtrue, mfalse: mfalse}
}
// Set sets a value in a specified column, with the given timestamp.
// The timestamp will be truncated to millisecond resolution.
// A timestamp of ServerTime means to use the server timestamp.
func (m *Mutation) Set(family, column string, ts Timestamp, value []byte) {
if ts != ServerTime {
// Truncate to millisecond resolution, since that's the default table config.
// TODO(dsymonds): Provide a way to override this behaviour.
ts -= ts % 1000
}
m.ops = append(m.ops, &btdpb.Mutation{Mutation: &btdpb.Mutation_SetCell_{&btdpb.Mutation_SetCell{
FamilyName: family,
ColumnQualifier: []byte(column),
TimestampMicros: int64(ts),
Value: value,
}}})
}
// DeleteCellsInColumn will delete all the cells whose columns are family:column.
func (m *Mutation) DeleteCellsInColumn(family, column string) {
m.ops = append(m.ops, &btdpb.Mutation{Mutation: &btdpb.Mutation_DeleteFromColumn_{&btdpb.Mutation_DeleteFromColumn{
FamilyName: family,
ColumnQualifier: []byte(column),
}}})
}
// DeleteTimestampRange deletes all cells whose columns are family:column
// and whose timestamps are in the half-open interval [start, end).
// If end is zero, it will be interpreted as infinity.
func (m *Mutation) DeleteTimestampRange(family, column string, start, end Timestamp) {
m.ops = append(m.ops, &btdpb.Mutation{Mutation: &btdpb.Mutation_DeleteFromColumn_{&btdpb.Mutation_DeleteFromColumn{
FamilyName: family,
ColumnQualifier: []byte(column),
TimeRange: &btdpb.TimestampRange{
StartTimestampMicros: int64(start),
EndTimestampMicros: int64(end),
},
}}})
}
// DeleteCellsInFamily will delete all the cells whose columns are family:*.
func (m *Mutation) DeleteCellsInFamily(family string) {
m.ops = append(m.ops, &btdpb.Mutation{Mutation: &btdpb.Mutation_DeleteFromFamily_{&btdpb.Mutation_DeleteFromFamily{
FamilyName: family,
}}})
}
// DeleteRow deletes the entire row.
func (m *Mutation) DeleteRow() {
m.ops = append(m.ops, &btdpb.Mutation{Mutation: &btdpb.Mutation_DeleteFromRow_{&btdpb.Mutation_DeleteFromRow{}}})
}
// Timestamp is in units of microseconds since 1 January 1970.
type Timestamp int64
// ServerTime is a specific Timestamp that may be passed to (*Mutation).Set.
// It indicates that the server's timestamp should be used.
const ServerTime Timestamp = -1
// Time converts a time.Time into a Timestamp.
func Time(t time.Time) Timestamp { return Timestamp(t.UnixNano() / 1e3) }
// Now returns the Timestamp representation of the current time on the client.
func Now() Timestamp { return Time(time.Now()) }
// Time converts a Timestamp into a time.Time.
func (ts Timestamp) Time() time.Time { return time.Unix(0, int64(ts)*1e3) }
// ApplyReadModifyWrite applies a ReadModifyWrite to a specific row.
// It returns the newly written cells.
func (t *Table) ApplyReadModifyWrite(ctx context.Context, row string, m *ReadModifyWrite) (Row, error) {
req := &btspb.ReadModifyWriteRowRequest{
TableName: t.c.fullTableName(t.table),
RowKey: []byte(row),
Rules: m.ops,
}
res, err := t.c.client.ReadModifyWriteRow(ctx, req)
if err != nil {
return nil, err
}
r := make(Row)
for _, fam := range res.Families { // res is *btdpb.Row, fam is *btdpb.Family
decodeFamilyProto(r, row, fam)
}
return r, nil
}
// ReadModifyWrite represents a set of operations on a single row of a table.
// It is like Mutation but for non-idempotent changes.
// When applied, these operations operate on the latest values of the row's cells,
// and result in a new value being written to the relevant cell with a timestamp
// that is max(existing timestamp, current server time).
//
// The application of a ReadModifyWrite is atomic; concurrent ReadModifyWrites will
// be executed serially by the server.
type ReadModifyWrite struct {
ops []*btdpb.ReadModifyWriteRule
}
// NewReadModifyWrite returns a new ReadModifyWrite.
func NewReadModifyWrite() *ReadModifyWrite { return new(ReadModifyWrite) }
// AppendValue appends a value to a specific cell's value.
// If the cell is unset, it will be treated as an empty value.
func (m *ReadModifyWrite) AppendValue(family, column string, v []byte) {
m.ops = append(m.ops, &btdpb.ReadModifyWriteRule{
FamilyName: family,
ColumnQualifier: []byte(column),
Rule: &btdpb.ReadModifyWriteRule_AppendValue{v},
})
}
// Increment interprets the value in a specific cell as a 64-bit big-endian signed integer,
// and adds a value to it. If the cell is unset, it will be treated as zero.
// If the cell is set and is not an 8-byte value, the entire ApplyReadModifyWrite
// operation will fail.
func (m *ReadModifyWrite) Increment(family, column string, delta int64) {
m.ops = append(m.ops, &btdpb.ReadModifyWriteRule{
FamilyName: family,
ColumnQualifier: []byte(column),
Rule: &btdpb.ReadModifyWriteRule_IncrementAmount{delta},
})
}

View file

@ -0,0 +1,606 @@
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bigtable
import (
"flag"
"fmt"
"math/rand"
"reflect"
"sort"
"strings"
"sync"
"testing"
"time"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"google.golang.org/cloud"
"google.golang.org/cloud/bigtable/bttest"
btspb "google.golang.org/cloud/bigtable/internal/service_proto"
"google.golang.org/grpc"
)
func dataChunk(fam, col string, ts int64, data string) string {
return fmt.Sprintf("chunks:<row_contents:<name:%q columns:<qualifier:%q cells:<timestamp_micros:%d value:%q>>>>", fam, col, ts, data)
}
func commit() string { return "chunks:<commit_row:true>" }
func reset() string { return "chunks:<reset_row:true>" }
var chunkTests = []struct {
desc string
chunks []string // sequence of ReadRowsResponse protos in text format
want map[string]Row
}{
{
desc: "single row single chunk",
chunks: []string{
`row_key: "row1" ` + dataChunk("fam", "col1", 1428382701000000, "data") + commit(),
},
want: map[string]Row{
"row1": Row{
"fam": []ReadItem{{
Row: "row1",
Column: "fam:col1",
Timestamp: 1428382701000000,
Value: []byte("data"),
}},
},
},
},
{
desc: "single row multiple chunks",
chunks: []string{
`row_key: "row1" ` + dataChunk("fam", "col1", 1428382701000000, "data"),
`row_key: "row1" ` + dataChunk("fam", "col2", 1428382702000000, "more data"),
`row_key: "row1" ` + commit(),
},
want: map[string]Row{
"row1": Row{
"fam": []ReadItem{
{
Row: "row1",
Column: "fam:col1",
Timestamp: 1428382701000000,
Value: []byte("data"),
},
{
Row: "row1",
Column: "fam:col2",
Timestamp: 1428382702000000,
Value: []byte("more data"),
},
},
},
},
},
{
desc: "chunk, reset, chunk, commit",
chunks: []string{
`row_key: "row1" ` + dataChunk("fam", "col1", 1428382701000000, "data"),
`row_key: "row1" ` + reset(),
`row_key: "row1" ` + dataChunk("fam", "col1", 1428382702000000, "data") + commit(),
},
want: map[string]Row{
"row1": Row{
"fam": []ReadItem{{
Row: "row1",
Column: "fam:col1",
Timestamp: 1428382702000000,
Value: []byte("data"),
}},
},
},
},
{
desc: "chunk, reset, commit",
chunks: []string{
`row_key: "row1" ` + dataChunk("fam", "col1", 1428382701000000, "data"),
`row_key: "row1" ` + reset(),
`row_key: "row1" ` + commit(),
},
want: map[string]Row{},
},
// TODO(dsymonds): More test cases, including
// - multiple rows
}
func TestChunkReader(t *testing.T) {
for _, tc := range chunkTests {
cr := new(chunkReader)
got := make(map[string]Row)
for i, txt := range tc.chunks {
rrr := new(btspb.ReadRowsResponse)
if err := proto.UnmarshalText(txt, rrr); err != nil {
t.Fatalf("%s: internal error: bad #%d test text: %v", tc.desc, i, err)
}
if row := cr.process(rrr); row != nil {
got[row.Key()] = row
}
}
// TODO(dsymonds): check for partial rows?
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("%s: processed response mismatch.\n got %+v\nwant %+v", tc.desc, got, tc.want)
}
}
}
func TestPrefix(t *testing.T) {
tests := []struct {
prefix, succ string
}{
{"", ""},
{"\xff", ""}, // when used, "" means Infinity
{"x\xff", "y"},
{"\xfe", "\xff"},
}
for _, tc := range tests {
got := prefixSuccessor(tc.prefix)
if got != tc.succ {
t.Errorf("prefixSuccessor(%q) = %q, want %s", tc.prefix, got, tc.succ)
continue
}
r := PrefixRange(tc.prefix)
if tc.succ == "" && r.limit != "" {
t.Errorf("PrefixRange(%q) got limit %q", tc.prefix, r.limit)
}
if tc.succ != "" && r.limit != tc.succ {
t.Errorf("PrefixRange(%q) got limit %q, want %q", tc.prefix, r.limit, tc.succ)
}
}
}
var useProd = flag.String("use_prod", "", `if set to "proj,zone,cluster,table", run integration test against production`)
func TestClientIntegration(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
}
proj, zone, cluster, table := "proj", "zone", "cluster", "mytable"
var clientOpts []cloud.ClientOption
timeout := 10 * time.Second
if *useProd == "" {
srv, err := bttest.NewServer()
if err != nil {
t.Fatal(err)
}
defer srv.Close()
t.Logf("bttest.Server running on %s", srv.Addr)
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
if err != nil {
t.Fatalf("grpc.Dial: %v", err)
}
clientOpts = []cloud.ClientOption{cloud.WithBaseGRPC(conn)}
} else {
t.Logf("Running test against production")
a := strings.Split(*useProd, ",")
proj, zone, cluster, table = a[0], a[1], a[2], a[3]
timeout = 5 * time.Minute
}
ctx, _ := context.WithTimeout(context.Background(), timeout)
client, err := NewClient(ctx, proj, zone, cluster, clientOpts...)
if err != nil {
t.Fatalf("NewClient: %v", err)
}
defer client.Close()
checkpoint("dialed Client")
adminClient, err := NewAdminClient(ctx, proj, zone, cluster, clientOpts...)
if err != nil {
t.Fatalf("NewAdminClient: %v", err)
}
defer adminClient.Close()
checkpoint("dialed AdminClient")
// 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)
if err := adminClient.CreateTable(ctx, table); err != nil {
t.Fatalf("Creating table: %v", err)
}
checkpoint("created table")
if err := adminClient.CreateColumnFamily(ctx, table, "follows"); err != nil {
t.Fatalf("Creating column family: %v", err)
}
checkpoint(`created "follows" column family`)
tbl := client.Open(table)
// Insert some data.
initialData := map[string][]string{
"wmckinley": []string{"tjefferson"},
"gwashington": []string{"jadams"},
"tjefferson": []string{"gwashington", "jadams"}, // wmckinley set conditionally below
"jadams": []string{"gwashington", "tjefferson"},
}
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")
// Do a conditional mutation with a complex filter.
mutTrue := NewMutation()
mutTrue.Set("follows", "wmckinley", 0, []byte("1"))
filter := ChainFilters(ColumnFilter("gwash[iz].*"), ValueFilter("."))
mut := NewCondMutation(filter, mutTrue, nil)
if err := tbl.Apply(ctx, "tjefferson", mut); err != nil {
t.Errorf("Conditionally mutating row: %v", err)
}
// Do a second condition mutation with a filter that does not match,
// and thus no changes should be made.
mutTrue = NewMutation()
mutTrue.DeleteRow()
filter = ColumnFilter("snoop.dogg")
mut = NewCondMutation(filter, mutTrue, nil)
if err := tbl.Apply(ctx, "tjefferson", mut); err != nil {
t.Errorf("Conditionally mutating row: %v", err)
}
checkpoint("did two conditional mutations")
// Fetch a row.
row, err := tbl.ReadRow(ctx, "jadams")
if err != nil {
t.Fatalf("Reading a row: %v", err)
}
wantRow := Row{
"follows": []ReadItem{
{Row: "jadams", Column: "follows:gwashington", Value: []byte("1")},
{Row: "jadams", Column: "follows:tjefferson", Value: []byte("1")},
},
}
for _, ris := range row {
sort.Sort(byColumn(ris))
}
if !reflect.DeepEqual(row, wantRow) {
t.Errorf("Read row mismatch.\n got %#v\nwant %#v", row, wantRow)
}
checkpoint("tested ReadRow")
// Do a bunch of reads with filters.
readTests := []struct {
desc string
rr RowRange
filter Filter // may be nil
// We do the read, grab all the cells, turn them into "<row>-<col>-<val>",
// sort that list, and join with a comma.
want string
}{
{
desc: "read all, unfiltered",
rr: RowRange{},
want: "gwashington-jadams-1,jadams-gwashington-1,jadams-tjefferson-1,tjefferson-gwashington-1,tjefferson-jadams-1,tjefferson-wmckinley-1,wmckinley-tjefferson-1",
},
{
desc: "read with InfiniteRange, unfiltered",
rr: InfiniteRange("tjefferson"),
want: "tjefferson-gwashington-1,tjefferson-jadams-1,tjefferson-wmckinley-1,wmckinley-tjefferson-1",
},
{
desc: "read with NewRange, unfiltered",
rr: NewRange("gargamel", "hubbard"),
want: "gwashington-jadams-1",
},
{
desc: "read with PrefixRange, unfiltered",
rr: PrefixRange("jad"),
want: "jadams-gwashington-1,jadams-tjefferson-1",
},
{
desc: "read with SingleRow, unfiltered",
rr: SingleRow("wmckinley"),
want: "wmckinley-tjefferson-1",
},
{
desc: "read all, with ColumnFilter",
rr: RowRange{},
filter: ColumnFilter(".*j.*"), // matches "jadams" and "tjefferson"
want: "gwashington-jadams-1,jadams-tjefferson-1,tjefferson-jadams-1,wmckinley-tjefferson-1",
},
}
for _, tc := range readTests {
var opts []ReadOption
if tc.filter != nil {
opts = append(opts, RowFilter(tc.filter))
}
var elt []string
err := tbl.ReadRows(context.Background(), tc.rr, func(r Row) bool {
for _, ris := range r {
for _, ri := range ris {
// Use the column qualifier only to make the test data briefer.
col := ri.Column[strings.Index(ri.Column, ":")+1:]
x := fmt.Sprintf("%s-%s-%s", ri.Row, col, ri.Value)
elt = append(elt, x)
}
}
return true
}, opts...)
if err != nil {
t.Errorf("%s: %v", tc.desc, err)
continue
}
sort.Strings(elt)
if got := strings.Join(elt, ","); got != tc.want {
t.Errorf("%s: wrong reads.\n got %q\nwant %q", tc.desc, got, tc.want)
}
}
checkpoint("tested ReadRows in a few ways")
// Do a scan and stop part way through.
// Verify that the ReadRows callback doesn't keep running.
stopped := false
err = tbl.ReadRows(ctx, InfiniteRange(""), func(r Row) bool {
if r.Key() < "h" {
return true
}
if !stopped {
stopped = true
return false
}
t.Errorf("ReadRows kept scanning to row %q after being told to stop", r.Key())
return false
})
if err != nil {
t.Errorf("Partial ReadRows: %v", err)
}
checkpoint("did partial ReadRows test")
// Delete a row and check it goes away.
mut = NewMutation()
mut.DeleteRow()
if err := tbl.Apply(ctx, "wmckinley", mut); err != nil {
t.Errorf("Apply DeleteRow: %v", err)
}
row, err = tbl.ReadRow(ctx, "wmckinley")
if err != nil {
t.Fatalf("Reading a row after DeleteRow: %v", err)
}
if len(row) != 0 {
t.Fatalf("Read non-zero row after DeleteRow: %v", row)
}
checkpoint("exercised DeleteRow")
// Check ReadModifyWrite.
if err := adminClient.CreateColumnFamily(ctx, table, "counter"); err != nil {
t.Fatalf("Creating column family: %v", err)
}
appendRMW := func(b []byte) *ReadModifyWrite {
rmw := NewReadModifyWrite()
rmw.AppendValue("counter", "likes", b)
return rmw
}
incRMW := func(n int64) *ReadModifyWrite {
rmw := NewReadModifyWrite()
rmw.Increment("counter", "likes", n)
return rmw
}
rmwSeq := []struct {
desc string
rmw *ReadModifyWrite
want []byte
}{
{
desc: "append #1",
rmw: appendRMW([]byte{0, 0, 0}),
want: []byte{0, 0, 0},
},
{
desc: "append #2",
rmw: appendRMW([]byte{0, 0, 0, 0, 17}), // the remaining 40 bits to make a big-endian 17
want: []byte{0, 0, 0, 0, 0, 0, 0, 17},
},
{
desc: "increment",
rmw: incRMW(8),
want: []byte{0, 0, 0, 0, 0, 0, 0, 25},
},
}
for _, step := range rmwSeq {
row, err := tbl.ApplyReadModifyWrite(ctx, "gwashington", step.rmw)
if err != nil {
t.Fatalf("ApplyReadModifyWrite %+v: %v", step.rmw, err)
}
clearTimestamps(row)
wantRow := Row{"counter": []ReadItem{{Row: "gwashington", Column: "counter:likes", Value: step.want}}}
if !reflect.DeepEqual(row, wantRow) {
t.Fatalf("After %s,\n got %v\nwant %v", step.desc, row, wantRow)
}
}
checkpoint("tested ReadModifyWrite")
// Test arbitrary timestamps more thoroughly.
if err := adminClient.CreateColumnFamily(ctx, table, "ts"); err != nil {
t.Fatalf("Creating column family: %v", err)
}
const numVersions = 4
mut = NewMutation()
for i := 0; i < numVersions; i++ {
// Timestamps are used in thousands because the server
// only permits that granularity.
mut.Set("ts", "col", Timestamp(i*1000), []byte(fmt.Sprintf("val-%d", i)))
}
if err := tbl.Apply(ctx, "testrow", mut); err != nil {
t.Fatalf("Mutating row: %v", err)
}
r, err := tbl.ReadRow(ctx, "testrow")
if err != nil {
t.Fatalf("Reading row: %v", err)
}
wantRow = Row{"ts": []ReadItem{
// These should be returned in descending timestamp order.
{Row: "testrow", Column: "ts:col", Timestamp: 3000, Value: []byte("val-3")},
{Row: "testrow", Column: "ts:col", Timestamp: 2000, Value: []byte("val-2")},
{Row: "testrow", Column: "ts:col", Timestamp: 1000, Value: []byte("val-1")},
{Row: "testrow", Column: "ts:col", Timestamp: 0, Value: []byte("val-0")},
}}
if !reflect.DeepEqual(r, wantRow) {
t.Errorf("Cell with multiple versions,\n got %v\nwant %v", r, wantRow)
}
// Do the same read, but filter to the latest two versions.
r, err = tbl.ReadRow(ctx, "testrow", RowFilter(LatestNFilter(2)))
if err != nil {
t.Fatalf("Reading row: %v", err)
}
wantRow = Row{"ts": []ReadItem{
{Row: "testrow", Column: "ts:col", Timestamp: 3000, Value: []byte("val-3")},
{Row: "testrow", Column: "ts:col", Timestamp: 2000, Value: []byte("val-2")},
}}
if !reflect.DeepEqual(r, wantRow) {
t.Errorf("Cell with multiple versions and LatestNFilter(2),\n got %v\nwant %v", r, wantRow)
}
// Delete the cell with timestamp 2000 and repeat the last read,
// checking that we get ts 3000 and ts 1000.
mut = NewMutation()
mut.DeleteTimestampRange("ts", "col", 2000, 3000) // half-open interval
if err := tbl.Apply(ctx, "testrow", mut); err != nil {
t.Fatalf("Mutating row: %v", err)
}
r, err = tbl.ReadRow(ctx, "testrow", RowFilter(LatestNFilter(2)))
if err != nil {
t.Fatalf("Reading row: %v", err)
}
wantRow = Row{"ts": []ReadItem{
{Row: "testrow", Column: "ts:col", Timestamp: 3000, Value: []byte("val-3")},
{Row: "testrow", Column: "ts:col", Timestamp: 1000, Value: []byte("val-1")},
}}
if !reflect.DeepEqual(r, wantRow) {
t.Errorf("Cell with multiple versions and LatestNFilter(2), after deleting timestamp 2000,\n got %v\nwant %v", r, wantRow)
}
checkpoint("tested multiple versions in a cell")
// Do highly concurrent reads/writes.
// TODO(dsymonds): Raise this to 1000 when https://github.com/grpc/grpc-go/issues/205 is resolved.
const maxConcurrency = 100
var wg sync.WaitGroup
for i := 0; i < maxConcurrency; i++ {
wg.Add(1)
go func() {
defer wg.Done()
switch r := rand.Intn(100); { // r ∈ [0,100)
case 0 <= r && r < 30:
// Do a read.
_, err := tbl.ReadRow(ctx, "testrow", RowFilter(LatestNFilter(1)))
if err != nil {
t.Errorf("Concurrent read: %v", err)
}
case 30 <= r && r < 100:
// Do a write.
mut := NewMutation()
mut.Set("ts", "col", 0, []byte("data"))
if err := tbl.Apply(ctx, "testrow", mut); err != nil {
t.Errorf("Concurrent write: %v", err)
}
}
}()
}
wg.Wait()
checkpoint("tested high concurrency")
// Large reads, writes and scans.
bigBytes := make([]byte, 15<<20) // 15 MB is large
nonsense := []byte("lorem ipsum dolor sit amet, ")
fill(bigBytes, nonsense)
mut = NewMutation()
mut.Set("ts", "col", 0, bigBytes)
if err := tbl.Apply(ctx, "bigrow", mut); err != nil {
t.Errorf("Big write: %v", err)
}
r, err = tbl.ReadRow(ctx, "bigrow")
if err != nil {
t.Errorf("Big read: %v", err)
}
wantRow = Row{"ts": []ReadItem{
{Row: "bigrow", Column: "ts:col", Value: bigBytes},
}}
if !reflect.DeepEqual(r, wantRow) {
t.Errorf("Big read returned incorrect bytes: %v", r)
}
// Now write 1000 rows, each with 82 KB values, then scan them all.
medBytes := make([]byte, 82<<10)
fill(medBytes, nonsense)
sem := make(chan int, 50) // do up to 50 mutations at a time.
for i := 0; i < 1000; i++ {
mut := NewMutation()
mut.Set("ts", "big-scan", 0, medBytes)
row := fmt.Sprintf("row-%d", i)
wg.Add(1)
go func() {
defer wg.Done()
defer func() { <-sem }()
sem <- 1
if err := tbl.Apply(ctx, row, mut); err != nil {
t.Errorf("Preparing large scan: %v", err)
}
}()
}
wg.Wait()
n := 0
err = tbl.ReadRows(ctx, PrefixRange("row-"), func(r Row) bool {
for _, ris := range r {
for _, ri := range ris {
n += len(ri.Value)
}
}
return true
}, RowFilter(ColumnFilter("big-scan")))
if err != nil {
t.Errorf("Doing large scan: %v", err)
}
if want := 1000 * len(medBytes); n != want {
t.Errorf("Large scan returned %d bytes, want %d", n, want)
}
checkpoint("tested big read/write/scan")
}
func fill(b, sub []byte) {
for len(b) > len(sub) {
n := copy(b, sub)
b = b[n:]
}
}
type byColumn []ReadItem
func (b byColumn) Len() int { return len(b) }
func (b byColumn) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byColumn) Less(i, j int) bool { return b[i].Column < b[j].Column }
func clearTimestamps(r Row) {
for _, ris := range r {
for i := range ris {
ris[i].Timestamp = 0
}
}
}

839
vendor/google.golang.org/cloud/bigtable/bttest/inmem.go generated vendored Normal file
View file

@ -0,0 +1,839 @@
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Package bttest contains test helpers for working with the bigtable package.
To use a Server, create it, and then connect to it with no security:
(The project/zone/cluster values are ignored.)
srv, err := bttest.NewServer()
...
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
...
client, err := bigtable.NewClient(ctx, proj, zone, cluster,
bigtable.WithBaseGRPC(conn))
...
*/
package bttest // import "google.golang.org/cloud/bigtable/bttest"
import (
"encoding/binary"
"fmt"
"log"
"math/rand"
"net"
"regexp"
"sort"
"strings"
"sync"
"time"
"golang.org/x/net/context"
btdpb "google.golang.org/cloud/bigtable/internal/data_proto"
emptypb "google.golang.org/cloud/bigtable/internal/empty"
btspb "google.golang.org/cloud/bigtable/internal/service_proto"
bttdpb "google.golang.org/cloud/bigtable/internal/table_data_proto"
bttspb "google.golang.org/cloud/bigtable/internal/table_service_proto"
"google.golang.org/grpc"
)
// Server is an in-memory Cloud Bigtable fake.
// It is unauthenticated, and only a rough approximation.
type Server struct {
Addr string
l net.Listener
srv *grpc.Server
s *server
}
// server is the real implementation of the fake.
// It is a separate and unexported type so the API won't be cluttered with
// methods that are only relevant to the fake's implementation.
type server struct {
mu sync.Mutex
tables map[string]*table // keyed by fully qualified name
gcc chan int // set when gcloop starts, closed when server shuts down
// Any unimplemented methods will cause a panic.
bttspb.BigtableTableServiceServer
btspb.BigtableServiceServer
}
// NewServer creates a new Server. The Server will be listening for gRPC connections
// at the address named by the Addr field, without TLS.
func NewServer() (*Server, error) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, err
}
s := &Server{
Addr: l.Addr().String(),
l: l,
srv: grpc.NewServer(),
s: &server{
tables: make(map[string]*table),
},
}
bttspb.RegisterBigtableTableServiceServer(s.srv, s.s)
btspb.RegisterBigtableServiceServer(s.srv, s.s)
go s.srv.Serve(s.l)
return s, nil
}
// Close shuts down the server.
func (s *Server) Close() {
s.s.mu.Lock()
if s.s.gcc != nil {
close(s.s.gcc)
}
s.s.mu.Unlock()
s.srv.Stop()
s.l.Close()
}
func (s *server) CreateTable(ctx context.Context, req *bttspb.CreateTableRequest) (*bttdpb.Table, error) {
tbl := req.Name + "/tables/" + req.TableId
s.mu.Lock()
if _, ok := s.tables[tbl]; ok {
s.mu.Unlock()
return nil, fmt.Errorf("table %q already exists", tbl)
}
s.tables[tbl] = newTable()
s.mu.Unlock()
return &bttdpb.Table{Name: tbl}, nil
}
func (s *server) ListTables(ctx context.Context, req *bttspb.ListTablesRequest) (*bttspb.ListTablesResponse, error) {
res := &bttspb.ListTablesResponse{}
prefix := req.Name + "/tables/"
s.mu.Lock()
for tbl := range s.tables {
if strings.HasPrefix(tbl, prefix) {
res.Tables = append(res.Tables, &bttdpb.Table{Name: tbl})
}
}
s.mu.Unlock()
return res, nil
}
func (s *server) GetTable(ctx context.Context, req *bttspb.GetTableRequest) (*bttdpb.Table, error) {
tbl := req.Name
s.mu.Lock()
tblIns, ok := s.tables[tbl]
s.mu.Unlock()
if !ok {
return nil, fmt.Errorf("table %q not found", tbl)
}
return &bttdpb.Table{
Name: tbl,
ColumnFamilies: toColumnFamilies(tblIns.families),
}, nil
}
func (s *server) DeleteTable(ctx context.Context, req *bttspb.DeleteTableRequest) (*emptypb.Empty, error) {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.tables[req.Name]; !ok {
return nil, fmt.Errorf("no such table %q", req.Name)
}
delete(s.tables, req.Name)
return &emptypb.Empty{}, nil
}
func (s *server) CreateColumnFamily(ctx context.Context, req *bttspb.CreateColumnFamilyRequest) (*bttdpb.ColumnFamily, error) {
s.mu.Lock()
tbl, ok := s.tables[req.Name]
s.mu.Unlock()
if !ok {
return nil, fmt.Errorf("no such table %q", req.Name)
}
// Check it is unique and record it.
fam := req.ColumnFamilyId
tbl.mu.Lock()
defer tbl.mu.Unlock()
if _, ok := tbl.families[fam]; ok {
return nil, fmt.Errorf("family %q already exists", fam)
}
newcf := &columnFamily{
name: req.Name + "/columnFamilies/" + fam,
}
tbl.families[fam] = newcf
return newcf.proto(), nil
}
func (s *server) UpdateColumnFamily(ctx context.Context, req *bttdpb.ColumnFamily) (*bttdpb.ColumnFamily, error) {
index := strings.Index(req.Name, "/columnFamilies/")
if index == -1 {
return nil, fmt.Errorf("bad family name %q", req.Name)
}
tblName := req.Name[:index]
fam := req.Name[index+len("/columnFamilies/"):]
s.mu.Lock()
tbl, ok := s.tables[tblName]
s.mu.Unlock()
if !ok {
return nil, fmt.Errorf("no such table %q", req.Name)
}
tbl.mu.Lock()
defer tbl.mu.Unlock()
// Check it is unique and record it.
if _, ok := tbl.families[fam]; !ok {
return nil, fmt.Errorf("no such family %q", fam)
}
newcf := &columnFamily{
name: req.Name,
gcRule: req.GcRule,
}
// assume that we ALWAYS want to replace by the new setting
// we may need partial update through
tbl.families[fam] = newcf
s.needGC()
return newcf.proto(), nil
}
func (s *server) ReadRows(req *btspb.ReadRowsRequest, stream btspb.BigtableService_ReadRowsServer) error {
s.mu.Lock()
tbl, ok := s.tables[req.TableName]
s.mu.Unlock()
if !ok {
return fmt.Errorf("no such table %q", req.TableName)
}
var start, end string // half-open interval
switch targ := req.Target.(type) {
case *btspb.ReadRowsRequest_RowRange:
start, end = string(targ.RowRange.StartKey), string(targ.RowRange.EndKey)
case *btspb.ReadRowsRequest_RowKey:
// A single row read is simply an edge case.
start = string(targ.RowKey)
end = start + "\x00"
default:
return fmt.Errorf("unknown ReadRowsRequest.Target oneof %T", targ)
}
// Get rows to stream back.
tbl.mu.RLock()
si, ei := 0, len(tbl.rows) // half-open interval
if start != "" {
si = sort.Search(len(tbl.rows), func(i int) bool { return tbl.rows[i].key >= start })
}
if end != "" {
ei = sort.Search(len(tbl.rows), func(i int) bool { return tbl.rows[i].key >= end })
}
if si >= ei {
tbl.mu.RUnlock()
return nil
}
rows := make([]*row, ei-si)
copy(rows, tbl.rows[si:ei])
tbl.mu.RUnlock()
for _, r := range rows {
if err := streamRow(stream, r, req.Filter); err != nil {
return err
}
}
return nil
}
func streamRow(stream btspb.BigtableService_ReadRowsServer, r *row, f *btdpb.RowFilter) error {
r.mu.Lock()
nr := r.copy()
r.mu.Unlock()
r = nr
filterRow(f, r)
rrr := &btspb.ReadRowsResponse{
RowKey: []byte(r.key),
}
for col, cells := range r.cells {
i := strings.Index(col, ":") // guaranteed to exist
fam, col := col[:i], col[i+1:]
if len(cells) == 0 {
continue
}
// TODO(dsymonds): Apply transformers.
colm := &btdpb.Column{
Qualifier: []byte(col),
// Cells is populated below.
}
for _, cell := range cells {
colm.Cells = append(colm.Cells, &btdpb.Cell{
TimestampMicros: cell.ts,
Value: cell.value,
})
}
rrr.Chunks = append(rrr.Chunks, &btspb.ReadRowsResponse_Chunk{
Chunk: &btspb.ReadRowsResponse_Chunk_RowContents{&btdpb.Family{
Name: fam,
Columns: []*btdpb.Column{colm},
}},
})
}
rrr.Chunks = append(rrr.Chunks, &btspb.ReadRowsResponse_Chunk{Chunk: &btspb.ReadRowsResponse_Chunk_CommitRow{true}})
return stream.Send(rrr)
}
// filterRow modifies a row with the given filter.
func filterRow(f *btdpb.RowFilter, r *row) {
if f == nil {
return
}
// Handle filters that apply beyond just including/excluding cells.
switch f := f.Filter.(type) {
case *btdpb.RowFilter_Chain_:
for _, sub := range f.Chain.Filters {
filterRow(sub, r)
}
return
case *btdpb.RowFilter_Interleave_:
srs := make([]*row, 0, len(f.Interleave.Filters))
for _, sub := range f.Interleave.Filters {
sr := r.copy()
filterRow(sub, sr)
srs = append(srs, sr)
}
// merge
// TODO(dsymonds): is this correct?
r.cells = make(map[string][]cell)
for _, sr := range srs {
for col, cs := range sr.cells {
r.cells[col] = append(r.cells[col], cs...)
}
}
for _, cs := range r.cells {
sort.Sort(byDescTS(cs))
}
return
case *btdpb.RowFilter_CellsPerColumnLimitFilter:
lim := int(f.CellsPerColumnLimitFilter)
for col, cs := range r.cells {
if len(cs) > lim {
r.cells[col] = cs[:lim]
}
}
return
}
// Any other case, operate on a per-cell basis.
for key, cs := range r.cells {
i := strings.Index(key, ":") // guaranteed to exist
fam, col := key[:i], key[i+1:]
r.cells[key] = filterCells(f, fam, col, cs)
}
}
func filterCells(f *btdpb.RowFilter, fam, col string, cs []cell) []cell {
var ret []cell
for _, cell := range cs {
if includeCell(f, fam, col, cell) {
ret = append(ret, cell)
}
}
return ret
}
func includeCell(f *btdpb.RowFilter, fam, col string, cell cell) bool {
if f == nil {
return true
}
// TODO(dsymonds): Implement many more filters.
switch f := f.Filter.(type) {
default:
log.Printf("WARNING: don't know how to handle filter of type %T (ignoring it)", f)
return true
case *btdpb.RowFilter_ColumnQualifierRegexFilter:
pat := string(f.ColumnQualifierRegexFilter)
rx, err := regexp.Compile(pat)
if err != nil {
log.Printf("Bad column_qualifier_regex_filter pattern %q: %v", pat, err)
return false
}
return rx.MatchString(col)
case *btdpb.RowFilter_ValueRegexFilter:
pat := string(f.ValueRegexFilter)
rx, err := regexp.Compile(pat)
if err != nil {
log.Printf("Bad value_regex_filter pattern %q: %v", pat, err)
return false
}
return rx.Match(cell.value)
}
}
func (s *server) MutateRow(ctx context.Context, req *btspb.MutateRowRequest) (*emptypb.Empty, error) {
s.mu.Lock()
tbl, ok := s.tables[req.TableName]
s.mu.Unlock()
if !ok {
return nil, fmt.Errorf("no such table %q", req.TableName)
}
r := tbl.mutableRow(string(req.RowKey))
r.mu.Lock()
defer r.mu.Unlock()
if err := applyMutations(tbl, r, req.Mutations); err != nil {
return nil, err
}
return &emptypb.Empty{}, nil
}
func (s *server) CheckAndMutateRow(ctx context.Context, req *btspb.CheckAndMutateRowRequest) (*btspb.CheckAndMutateRowResponse, error) {
s.mu.Lock()
tbl, ok := s.tables[req.TableName]
s.mu.Unlock()
if !ok {
return nil, fmt.Errorf("no such table %q", req.TableName)
}
res := &btspb.CheckAndMutateRowResponse{}
r := tbl.mutableRow(string(req.RowKey))
r.mu.Lock()
defer r.mu.Unlock()
// Figure out which mutation to apply.
whichMut := false
if req.PredicateFilter == nil {
// Use true_mutations iff row contains any cells.
whichMut = len(r.cells) > 0
} else {
// Use true_mutations iff any cells in the row match the filter.
// TODO(dsymonds): This could be cheaper.
nr := r.copy()
filterRow(req.PredicateFilter, nr)
for _, cs := range nr.cells {
if len(cs) > 0 {
whichMut = true
break
}
}
// TODO(dsymonds): Figure out if this is supposed to be set
// even when there's no predicate filter.
res.PredicateMatched = whichMut
}
muts := req.FalseMutations
if whichMut {
muts = req.TrueMutations
}
if err := applyMutations(tbl, r, muts); err != nil {
return nil, err
}
return res, nil
}
// applyMutations applies a sequence of mutations to a row.
// It assumes r.mu is locked.
func applyMutations(tbl *table, r *row, muts []*btdpb.Mutation) error {
for _, mut := range muts {
switch mut := mut.Mutation.(type) {
default:
return fmt.Errorf("can't handle mutation type %T", mut)
case *btdpb.Mutation_SetCell_:
set := mut.SetCell
tbl.mu.RLock()
_, famOK := tbl.families[set.FamilyName]
tbl.mu.RUnlock()
if !famOK {
return fmt.Errorf("unknown family %q", set.FamilyName)
}
ts := set.TimestampMicros
if ts == -1 { // bigtable.ServerTime
ts = time.Now().UnixNano() / 1e3
ts -= ts % 1000 // round to millisecond granularity
}
if !tbl.validTimestamp(ts) {
return fmt.Errorf("invalid timestamp %d", ts)
}
col := fmt.Sprintf("%s:%s", set.FamilyName, set.ColumnQualifier)
cs := r.cells[col]
newCell := cell{ts: ts, value: set.Value}
replaced := false
for i, cell := range cs {
if cell.ts == newCell.ts {
cs[i] = newCell
replaced = true
break
}
}
if !replaced {
cs = append(cs, newCell)
}
sort.Sort(byDescTS(cs))
r.cells[col] = cs
case *btdpb.Mutation_DeleteFromColumn_:
del := mut.DeleteFromColumn
col := fmt.Sprintf("%s:%s", del.FamilyName, del.ColumnQualifier)
cs := r.cells[col]
if del.TimeRange != nil {
tsr := del.TimeRange
if !tbl.validTimestamp(tsr.StartTimestampMicros) {
return fmt.Errorf("invalid timestamp %d", tsr.StartTimestampMicros)
}
if !tbl.validTimestamp(tsr.EndTimestampMicros) {
return fmt.Errorf("invalid timestamp %d", tsr.EndTimestampMicros)
}
// Find half-open interval to remove.
// Cells are in descending timestamp order,
// so the predicates to sort.Search are inverted.
si, ei := 0, len(cs)
if tsr.StartTimestampMicros > 0 {
ei = sort.Search(len(cs), func(i int) bool { return cs[i].ts < tsr.StartTimestampMicros })
}
if tsr.EndTimestampMicros > 0 {
si = sort.Search(len(cs), func(i int) bool { return cs[i].ts < tsr.EndTimestampMicros })
}
if si < ei {
copy(cs[si:], cs[ei:])
cs = cs[:len(cs)-(ei-si)]
}
} else {
cs = nil
}
if len(cs) == 0 {
delete(r.cells, col)
} else {
r.cells[col] = cs
}
case *btdpb.Mutation_DeleteFromRow_:
r.cells = make(map[string][]cell)
}
}
return nil
}
func (s *server) ReadModifyWriteRow(ctx context.Context, req *btspb.ReadModifyWriteRowRequest) (*btdpb.Row, error) {
s.mu.Lock()
tbl, ok := s.tables[req.TableName]
s.mu.Unlock()
if !ok {
return nil, fmt.Errorf("no such table %q", req.TableName)
}
updates := make(map[string]cell) // copy of updated cells; keyed by full column name
r := tbl.mutableRow(string(req.RowKey))
r.mu.Lock()
defer r.mu.Unlock()
// Assume all mutations apply to the most recent version of the cell.
// TODO(dsymonds): Verify this assumption and document it in the proto.
for _, rule := range req.Rules {
tbl.mu.RLock()
_, famOK := tbl.families[rule.FamilyName]
tbl.mu.RUnlock()
if !famOK {
return nil, fmt.Errorf("unknown family %q", rule.FamilyName)
}
key := fmt.Sprintf("%s:%s", rule.FamilyName, rule.ColumnQualifier)
newCell := false
if len(r.cells[key]) == 0 {
r.cells[key] = []cell{{
// TODO(dsymonds): should this set a timestamp?
}}
newCell = true
}
cell := &r.cells[key][0]
switch rule := rule.Rule.(type) {
default:
return nil, fmt.Errorf("unknown RMW rule oneof %T", rule)
case *btdpb.ReadModifyWriteRule_AppendValue:
cell.value = append(cell.value, rule.AppendValue...)
case *btdpb.ReadModifyWriteRule_IncrementAmount:
var v int64
if !newCell {
if len(cell.value) != 8 {
return nil, fmt.Errorf("increment on non-64-bit value")
}
v = int64(binary.BigEndian.Uint64(cell.value))
}
v += rule.IncrementAmount
var val [8]byte
binary.BigEndian.PutUint64(val[:], uint64(v))
cell.value = val[:]
}
updates[key] = *cell
}
res := &btdpb.Row{
Key: req.RowKey,
}
for col, cell := range updates {
i := strings.Index(col, ":")
fam, qual := col[:i], col[i+1:]
var f *btdpb.Family
for _, ff := range res.Families {
if ff.Name == fam {
f = ff
break
}
}
if f == nil {
f = &btdpb.Family{Name: fam}
res.Families = append(res.Families, f)
}
f.Columns = append(f.Columns, &btdpb.Column{
Qualifier: []byte(qual),
Cells: []*btdpb.Cell{{
Value: cell.value,
}},
})
}
return res, nil
}
// needGC is invoked whenever the server needs gcloop running.
func (s *server) needGC() {
s.mu.Lock()
if s.gcc == nil {
s.gcc = make(chan int)
go s.gcloop(s.gcc)
}
s.mu.Unlock()
}
func (s *server) gcloop(done <-chan int) {
const (
minWait = 500 // ms
maxWait = 1500 // ms
)
for {
// Wait for a random time interval.
d := time.Duration(minWait+rand.Intn(maxWait-minWait)) * time.Millisecond
select {
case <-time.After(d):
case <-done:
return // server has been closed
}
// Do a GC pass over all tables.
var tables []*table
s.mu.Lock()
for _, tbl := range s.tables {
tables = append(tables, tbl)
}
s.mu.Unlock()
for _, tbl := range tables {
tbl.gc()
}
}
}
type table struct {
mu sync.RWMutex
families map[string]*columnFamily // keyed by plain family name
rows []*row // sorted by row key
rowIndex map[string]*row // indexed by row key
}
func newTable() *table {
return &table{
families: make(map[string]*columnFamily),
rowIndex: make(map[string]*row),
}
}
func (t *table) validTimestamp(ts int64) bool {
// Assume millisecond granularity is required.
return ts%1000 == 0
}
func (t *table) mutableRow(row string) *row {
// Try fast path first.
t.mu.RLock()
r := t.rowIndex[row]
t.mu.RUnlock()
if r != nil {
return r
}
// We probably need to create the row.
t.mu.Lock()
r = t.rowIndex[row]
if r == nil {
r = newRow(row)
t.rowIndex[row] = r
t.rows = append(t.rows, r)
sort.Sort(byRowKey(t.rows)) // yay, inefficient!
}
t.mu.Unlock()
return r
}
func (t *table) gc() {
// This method doesn't add or remove rows, so we only need a read lock for the table.
t.mu.RLock()
defer t.mu.RUnlock()
// Gather GC rules we'll apply.
rules := make(map[string]*bttdpb.GcRule) // keyed by "fam"
for fam, cf := range t.families {
if cf.gcRule != nil {
rules[fam] = cf.gcRule
}
}
if len(rules) == 0 {
return
}
for _, r := range t.rows {
r.mu.Lock()
r.gc(rules)
r.mu.Unlock()
}
}
type byRowKey []*row
func (b byRowKey) Len() int { return len(b) }
func (b byRowKey) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byRowKey) Less(i, j int) bool { return b[i].key < b[j].key }
type row struct {
key string
mu sync.Mutex
cells map[string][]cell // keyed by full column name; cells are in descending timestamp order
}
func newRow(key string) *row {
return &row{
key: key,
cells: make(map[string][]cell),
}
}
// copy returns a copy of the row.
// Cell values are aliased.
// r.mu should be held.
func (r *row) copy() *row {
nr := &row{
key: r.key,
cells: make(map[string][]cell, len(r.cells)),
}
for col, cs := range r.cells {
// Copy the []cell slice, but not the []byte inside each cell.
nr.cells[col] = append([]cell(nil), cs...)
}
return nr
}
// gc applies the given GC rules to the row.
// r.mu should be held.
func (r *row) gc(rules map[string]*bttdpb.GcRule) {
for col, cs := range r.cells {
fam := col[:strings.Index(col, ":")]
rule, ok := rules[fam]
if !ok {
continue
}
r.cells[col] = applyGC(cs, rule)
}
}
var gcTypeWarn sync.Once
// applyGC applies the given GC rule to the cells.
func applyGC(cells []cell, rule *bttdpb.GcRule) []cell {
switch rule := rule.Rule.(type) {
default:
// TODO(dsymonds): Support GcRule_Intersection_
gcTypeWarn.Do(func() {
log.Printf("Unsupported GC rule type %T", rule)
})
case *bttdpb.GcRule_Union_:
for _, sub := range rule.Union.Rules {
cells = applyGC(cells, sub)
}
return cells
case *bttdpb.GcRule_MaxAge:
// Timestamps are in microseconds.
cutoff := time.Now().UnixNano() / 1e3
cutoff -= rule.MaxAge.Seconds * 1e6
cutoff -= int64(rule.MaxAge.Nanos) / 1e3
// The slice of cells in in descending timestamp order.
// This sort.Search will return the index of the first cell whose timestamp is chronologically before the cutoff.
si := sort.Search(len(cells), func(i int) bool { return cells[i].ts < cutoff })
if si < len(cells) {
log.Printf("bttest: GC MaxAge(%v) deleted %d cells.", rule.MaxAge, len(cells)-si)
}
return cells[:si]
case *bttdpb.GcRule_MaxNumVersions:
n := int(rule.MaxNumVersions)
if len(cells) > n {
log.Printf("bttest: GC MaxNumVersions(%d) deleted %d cells.", n, len(cells)-n)
cells = cells[:n]
}
return cells
}
return cells
}
type cell struct {
ts int64
value []byte
}
type byDescTS []cell
func (b byDescTS) Len() int { return len(b) }
func (b byDescTS) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byDescTS) Less(i, j int) bool { return b[i].ts > b[j].ts }
type columnFamily struct {
name string
gcRule *bttdpb.GcRule
}
func (c *columnFamily) proto() *bttdpb.ColumnFamily {
return &bttdpb.ColumnFamily{
Name: c.name,
GcRule: c.gcRule,
}
}
func toColumnFamilies(families map[string]*columnFamily) map[string]*bttdpb.ColumnFamily {
f := make(map[string]*bttdpb.ColumnFamily)
for k, v := range families {
f[k] = v.proto()
}
return f
}

580
vendor/google.golang.org/cloud/bigtable/cmd/cbt/cbt.go generated vendored Normal file
View file

@ -0,0 +1,580 @@
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
// Command docs are in cbtdoc.go.
import (
"bytes"
"flag"
"fmt"
"go/format"
"log"
"os"
"regexp"
"sort"
"strconv"
"strings"
"text/tabwriter"
"text/template"
"time"
"golang.org/x/net/context"
"google.golang.org/cloud/bigtable"
"google.golang.org/cloud/bigtable/internal/cbtrc"
)
var (
oFlag = flag.String("o", "", "if set, redirect stdout to this file")
config *cbtrc.Config
client *bigtable.Client
adminClient *bigtable.AdminClient
clusterAdminClient *bigtable.ClusterAdminClient
)
func getClient() *bigtable.Client {
if client == nil {
var err error
client, err = bigtable.NewClient(context.Background(), config.Project, config.Zone, config.Cluster)
if err != nil {
log.Fatalf("Making bigtable.Client: %v", err)
}
}
return client
}
func getAdminClient() *bigtable.AdminClient {
if adminClient == nil {
var err error
adminClient, err = bigtable.NewAdminClient(context.Background(), config.Project, config.Zone, config.Cluster)
if err != nil {
log.Fatalf("Making bigtable.AdminClient: %v", err)
}
}
return adminClient
}
func getClusterAdminClient() *bigtable.ClusterAdminClient {
if clusterAdminClient == nil {
var err error
clusterAdminClient, err = bigtable.NewClusterAdminClient(context.Background(), config.Project)
if err != nil {
log.Fatalf("Making bigtable.ClusterAdminClient: %v", err)
}
}
return clusterAdminClient
}
func main() {
var err error
config, err = cbtrc.Load()
if err != nil {
log.Fatal(err)
}
config.RegisterFlags()
flag.Usage = usage
flag.Parse()
if err := config.CheckFlags(); err != nil {
log.Fatal(err)
}
if config.Creds != "" {
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", config.Creds)
}
if flag.NArg() == 0 {
usage()
os.Exit(1)
}
if *oFlag != "" {
f, err := os.Create(*oFlag)
if err != nil {
log.Fatal(err)
}
defer func() {
if err := f.Close(); err != nil {
log.Fatal(err)
}
}()
os.Stdout = f
}
ctx := context.Background()
for _, cmd := range commands {
if cmd.Name == flag.Arg(0) {
cmd.do(ctx, flag.Args()[1:]...)
return
}
}
log.Fatalf("Unknown command %q", flag.Arg(0))
}
func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s [flags] <command> ...\n", os.Args[0])
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\n%s", cmdSummary)
}
var cmdSummary string // generated in init, below
func init() {
var buf bytes.Buffer
tw := tabwriter.NewWriter(&buf, 10, 8, 4, '\t', 0)
for _, cmd := range commands {
fmt.Fprintf(tw, "cbt %s\t%s\n", cmd.Name, cmd.Desc)
}
tw.Flush()
buf.WriteString(configHelp)
cmdSummary = buf.String()
}
var configHelp = `
For convenience, values of the -project, -zone, -cluster and -creds flags
may be specified in ` + cbtrc.Filename() + ` in this format:
project = my-project-123
zone = us-central1-b
cluster = my-cluster
creds = path-to-account-key.json
All values are optional, and all will be overridden by flags.
`
var commands = []struct {
Name, Desc string
do func(context.Context, ...string)
Usage string
}{
{
Name: "count",
Desc: "Count rows in a table",
do: doCount,
Usage: "cbt count <table>",
},
{
Name: "createfamily",
Desc: "Create a column family",
do: doCreateFamily,
Usage: "cbt createfamily <table> <family>",
},
{
Name: "createtable",
Desc: "Create a table",
do: doCreateTable,
Usage: "cbt createtable <table>",
},
{
Name: "deletefamily",
Desc: "Delete a column family",
do: doDeleteFamily,
Usage: "cbt deletefamily <table> <family>",
},
{
Name: "deleterow",
Desc: "Delete a row",
do: doDeleteRow,
Usage: "cbt deleterow <table> <row>",
},
{
Name: "deletetable",
Desc: "Delete a table",
do: doDeleteTable,
Usage: "cbt deletetable <table>",
},
{
Name: "doc",
Desc: "Print documentation for cbt",
do: doDoc,
Usage: "cbt doc",
},
{
Name: "help",
Desc: "Print help text",
do: doHelp,
Usage: "cbt help [command]",
},
{
Name: "listclusters",
Desc: "List clusters in a project",
do: doListClusters,
Usage: "cbt listclusters",
},
{
Name: "lookup",
Desc: "Read from a single row",
do: doLookup,
Usage: "cbt lookup <table> <row>",
},
{
Name: "ls",
Desc: "List tables and column families",
do: doLS,
Usage: "cbt ls List tables\n" +
"cbt ls <table> List column families in <table>",
},
{
Name: "read",
Desc: "Read rows",
do: doRead,
Usage: "cbt read <table> [start=<row>] [limit=<row>] [prefix=<prefix>]\n" +
" start=<row> Start reading at this row\n" +
" limit=<row> Stop reading before this row\n" +
" prefix=<prefix> Read rows with this prefix\n",
},
{
Name: "set",
Desc: "Set value of a cell",
do: doSet,
Usage: "cbt set <table> <row> family:column=val[@ts] ...\n" +
" family:column=val[@ts] may be repeated to set multiple cells.\n" +
"\n" +
" ts is an optional integer timestamp.\n" +
" If it cannot be parsed, the `@ts` part will be\n" +
" interpreted as part of the value.",
},
/* TODO(dsymonds): Re-enable when there's a ClusterAdmin API.
{
Name: "setclustersize",
Desc: "Set size of a cluster",
do: doSetClusterSize,
Usage: "cbt setclustersize <num_nodes>",
},
*/
}
func doCount(ctx context.Context, args ...string) {
if len(args) != 1 {
log.Fatal("usage: cbt count <table>")
}
tbl := getClient().Open(args[0])
n := 0
err := tbl.ReadRows(ctx, bigtable.InfiniteRange(""), func(_ bigtable.Row) bool {
n++
return true
}, bigtable.RowFilter(bigtable.StripValueFilter()))
if err != nil {
log.Fatalf("Reading rows: %v", err)
}
fmt.Println(n)
}
func doCreateFamily(ctx context.Context, args ...string) {
if len(args) != 2 {
log.Fatal("usage: cbt createfamily <table> <family>")
}
err := getAdminClient().CreateColumnFamily(ctx, args[0], args[1])
if err != nil {
log.Fatalf("Creating column family: %v", err)
}
}
func doCreateTable(ctx context.Context, args ...string) {
if len(args) != 1 {
log.Fatal("usage: cbt createtable <table>")
}
err := getAdminClient().CreateTable(ctx, args[0])
if err != nil {
log.Fatalf("Creating table: %v", err)
}
}
func doDeleteFamily(ctx context.Context, args ...string) {
if len(args) != 2 {
log.Fatal("usage: cbt deletefamily <table> <family>")
}
err := getAdminClient().DeleteColumnFamily(ctx, args[0], args[1])
if err != nil {
log.Fatalf("Deleting column family: %v", err)
}
}
func doDeleteRow(ctx context.Context, args ...string) {
if len(args) != 2 {
log.Fatal("usage: cbt deleterow <table> <row>")
}
tbl := getClient().Open(args[0])
mut := bigtable.NewMutation()
mut.DeleteRow()
if err := tbl.Apply(ctx, args[1], mut); err != nil {
log.Fatalf("Deleting row: %v", err)
}
}
func doDeleteTable(ctx context.Context, args ...string) {
if len(args) != 1 {
log.Fatalf("Can't do `cbt deletetable %s`", args)
}
err := getAdminClient().DeleteTable(ctx, args[0])
if err != nil {
log.Fatalf("Deleting table: %v", err)
}
}
// to break circular dependencies
var (
doDocFn func(ctx context.Context, args ...string)
doHelpFn func(ctx context.Context, args ...string)
)
func init() {
doDocFn = doDocReal
doHelpFn = doHelpReal
}
func doDoc(ctx context.Context, args ...string) { doDocFn(ctx, args...) }
func doHelp(ctx context.Context, args ...string) { doHelpFn(ctx, args...) }
func doDocReal(ctx context.Context, args ...string) {
data := map[string]interface{}{
"Commands": commands,
}
var buf bytes.Buffer
if err := docTemplate.Execute(&buf, data); err != nil {
log.Fatalf("Bad doc template: %v", err)
}
out, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("Bad doc output: %v", err)
}
os.Stdout.Write(out)
}
var docTemplate = template.Must(template.New("doc").Funcs(template.FuncMap{
"indent": func(s, ind string) string {
ss := strings.Split(s, "\n")
for i, p := range ss {
ss[i] = ind + p
}
return strings.Join(ss, "\n")
},
}).
Parse(`
// DO NOT EDIT. THIS IS AUTOMATICALLY GENERATED.
// Run "go generate" to regenerate.
//go:generate go run cbt.go -o cbtdoc.go doc
/*
Cbt is a tool for doing basic interactions with Cloud Bigtable.
Usage:
cbt [options] command [arguments]
The commands are:
{{range .Commands}}
{{printf "%-25s %s" .Name .Desc}}{{end}}
Use "cbt help <command>" for more information about a command.
{{range .Commands}}
{{.Desc}}
Usage:
{{indent .Usage "\t"}}
{{end}}
*/
package main
`))
func doHelpReal(ctx context.Context, args ...string) {
if len(args) == 0 {
fmt.Print(cmdSummary)
return
}
for _, cmd := range commands {
if cmd.Name == args[0] {
fmt.Println(cmd.Usage)
return
}
}
log.Fatalf("Don't know command %q", args[0])
}
func doListClusters(ctx context.Context, args ...string) {
if len(args) != 0 {
log.Fatalf("usage: cbt listclusters")
}
cis, err := getClusterAdminClient().Clusters(ctx)
if err != nil {
log.Fatalf("Getting list of clusters: %v", err)
}
tw := tabwriter.NewWriter(os.Stdout, 10, 8, 4, '\t', 0)
fmt.Fprintf(tw, "Cluster Name\tZone\tInfo\n")
fmt.Fprintf(tw, "------------\t----\t----\n")
for _, ci := range cis {
fmt.Fprintf(tw, "%s\t%s\t%s (%d serve nodes)\n", ci.Name, ci.Zone, ci.DisplayName, ci.ServeNodes)
}
tw.Flush()
}
func doLookup(ctx context.Context, args ...string) {
if len(args) != 2 {
log.Fatalf("usage: cbt lookup <table> <row>")
}
table, row := args[0], args[1]
tbl := getClient().Open(table)
r, err := tbl.ReadRow(ctx, row)
if err != nil {
log.Fatalf("Reading row: %v", err)
}
printRow(r)
}
func printRow(r bigtable.Row) {
fmt.Println(strings.Repeat("-", 40))
fmt.Println(r.Key())
var fams []string
for fam := range r {
fams = append(fams, fam)
}
sort.Strings(fams)
for _, fam := range fams {
ris := r[fam]
sort.Sort(byColumn(ris))
for _, ri := range ris {
ts := time.Unix(0, int64(ri.Timestamp)*1e3)
fmt.Printf(" %-40s @ %s\n", ri.Column, ts.Format("2006/01/02-15:04:05.000000"))
fmt.Printf(" %q\n", ri.Value)
}
}
}
type byColumn []bigtable.ReadItem
func (b byColumn) Len() int { return len(b) }
func (b byColumn) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byColumn) Less(i, j int) bool { return b[i].Column < b[j].Column }
func doLS(ctx context.Context, args ...string) {
switch len(args) {
default:
log.Fatalf("Can't do `cbt ls %s`", args)
case 0:
tables, err := getAdminClient().Tables(ctx)
if err != nil {
log.Fatalf("Getting list of tables: %v", err)
}
sort.Strings(tables)
for _, table := range tables {
fmt.Println(table)
}
case 1:
table := args[0]
ti, err := getAdminClient().TableInfo(ctx, table)
if err != nil {
log.Fatalf("Getting table info: %v", err)
}
sort.Strings(ti.Families)
for _, fam := range ti.Families {
fmt.Println(fam)
}
}
}
func doRead(ctx context.Context, args ...string) {
if len(args) < 1 {
log.Fatalf("usage: cbt read <table> [args ...]")
}
tbl := getClient().Open(args[0])
parsed := make(map[string]string)
for _, arg := range args[1:] {
i := strings.Index(arg, "=")
if i < 0 {
log.Fatalf("Bad arg %q", arg)
}
key, val := arg[:i], arg[i+1:]
switch key {
default:
log.Fatalf("Unknown arg key %q", key)
case "start", "limit", "prefix":
parsed[key] = val
}
}
if (parsed["start"] != "" || parsed["limit"] != "") && parsed["prefix"] != "" {
log.Fatal(`"start"/"limit" may not be mixed with "prefix"`)
}
var rr bigtable.RowRange
if start, limit := parsed["start"], parsed["limit"]; limit != "" {
rr = bigtable.NewRange(start, limit)
} else if start != "" {
rr = bigtable.InfiniteRange(start)
}
if prefix := parsed["prefix"]; prefix != "" {
rr = bigtable.PrefixRange(prefix)
}
// TODO(dsymonds): Support filters.
err := tbl.ReadRows(ctx, rr, func(r bigtable.Row) bool {
printRow(r)
return true
})
if err != nil {
log.Fatalf("Reading rows: %v", err)
}
}
var setArg = regexp.MustCompile(`([^:]+):([^=]*)=(.*)`)
func doSet(ctx context.Context, args ...string) {
if len(args) < 3 {
log.Fatalf("usage: cbt set <table> <row> family:[column]=val[@ts] ...")
}
tbl := getClient().Open(args[0])
row := args[1]
mut := bigtable.NewMutation()
for _, arg := range args[2:] {
m := setArg.FindStringSubmatch(arg)
if m == nil {
log.Fatalf("Bad set arg %q", arg)
}
val := m[3]
ts := bigtable.Now()
if i := strings.LastIndex(val, "@"); i >= 0 {
// Try parsing a timestamp.
n, err := strconv.ParseInt(val[i+1:], 0, 64)
if err == nil {
val = val[:i]
ts = bigtable.Timestamp(n)
}
}
mut.Set(m[1], m[2], ts, []byte(val))
}
if err := tbl.Apply(ctx, row, mut); err != nil {
log.Fatalf("Applying mutation: %v", err)
}
}
/* TODO(dsymonds): Re-enable when there's a ClusterAdmin API.
func doSetClusterSize(ctx context.Context, args ...string) {
if len(args) != 1 {
log.Fatalf("usage: cbt setclustersize <num_nodes>")
}
n, err := strconv.ParseInt(args[0], 0, 32)
if err != nil {
log.Fatalf("Bad num_nodes value %q: %v", args[0], err)
}
if err := getAdminClient().SetClusterSize(ctx, int(n)); err != nil {
log.Fatalf("Setting cluster size: %v", err)
}
}
*/

View file

@ -0,0 +1,146 @@
// DO NOT EDIT. THIS IS AUTOMATICALLY GENERATED.
// Run "go generate" to regenerate.
//go:generate go run cbt.go -o cbtdoc.go doc
/*
Cbt is a tool for doing basic interactions with Cloud Bigtable.
Usage:
cbt [options] command [arguments]
The commands are:
count Count rows in a table
createfamily Create a column family
createtable Create a table
deletefamily Delete a column family
deleterow Delete a row
deletetable Delete a table
doc Print documentation for cbt
help Print help text
listclusters List clusters in a project
lookup Read from a single row
ls List tables and column families
read Read rows
set Set value of a cell
Use "cbt help <command>" for more information about a command.
Count rows in a table
Usage:
cbt count <table>
Create a column family
Usage:
cbt createfamily <table> <family>
Create a table
Usage:
cbt createtable <table>
Delete a column family
Usage:
cbt deletefamily <table> <family>
Delete a row
Usage:
cbt deleterow <table> <row>
Delete a table
Usage:
cbt deletetable <table>
Print documentation for cbt
Usage:
cbt doc
Print help text
Usage:
cbt help [command]
List clusters in a project
Usage:
cbt listclusters
Read from a single row
Usage:
cbt lookup <table> <row>
List tables and column families
Usage:
cbt ls List tables
cbt ls <table> List column families in <table>
Read rows
Usage:
cbt read <table> [start=<row>] [limit=<row>] [prefix=<prefix>]
start=<row> Start reading at this row
limit=<row> Stop reading before this row
prefix=<prefix> Read rows with this prefix
Set value of a cell
Usage:
cbt set <table> <row> family:column=val[@ts] ...
family:column=val[@ts] may be repeated to set multiple cells.
ts is an optional integer timestamp.
If it cannot be parsed, the `@ts` part will be
interpreted as part of the value.
*/
package main

View file

@ -0,0 +1,159 @@
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Loadtest does some load testing through the Go client library for Cloud Bigtable.
*/
package main
import (
"bytes"
"flag"
"fmt"
"log"
"math/rand"
"os"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/context"
"google.golang.org/cloud/bigtable"
"google.golang.org/cloud/bigtable/internal/cbtrc"
)
var (
runFor = flag.Duration("run_for", 5*time.Second, "how long to run the load test for")
scratchTable = flag.String("scratch_table", "loadtest-scratch", "name of table to use; should not already exist")
config *cbtrc.Config
client *bigtable.Client
adminClient *bigtable.AdminClient
)
func main() {
var err error
config, err = cbtrc.Load()
if err != nil {
log.Fatal(err)
}
config.RegisterFlags()
flag.Parse()
if err := config.CheckFlags(); err != nil {
log.Fatal(err)
}
if config.Creds != "" {
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", config.Creds)
}
if flag.NArg() != 0 {
flag.Usage()
os.Exit(1)
}
log.Printf("Dialing connections...")
client, err = bigtable.NewClient(context.Background(), config.Project, config.Zone, config.Cluster)
if err != nil {
log.Fatalf("Making bigtable.Client: %v", err)
}
defer client.Close()
adminClient, err = bigtable.NewAdminClient(context.Background(), config.Project, config.Zone, config.Cluster)
if err != nil {
log.Fatalf("Making bigtable.AdminClient: %v", err)
}
defer adminClient.Close()
// Create a scratch table.
log.Printf("Setting up scratch table...")
if err := adminClient.CreateTable(context.Background(), *scratchTable); err != nil {
log.Fatalf("Making scratch table %q: %v", *scratchTable, err)
}
if err := adminClient.CreateColumnFamily(context.Background(), *scratchTable, "f"); err != nil {
log.Fatalf("Making scratch table column family: %v", err)
}
// Upon a successful run, delete the table. Don't bother checking for errors.
defer adminClient.DeleteTable(context.Background(), *scratchTable)
log.Printf("Starting load test... (run for %v)", *runFor)
tbl := client.Open(*scratchTable)
sem := make(chan int, 100) // limit the number of requests happening at once
var reads, writes stats
stopTime := time.Now().Add(*runFor)
var wg sync.WaitGroup
for time.Now().Before(stopTime) {
sem <- 1
wg.Add(1)
go func() {
defer wg.Done()
defer func() { <-sem }()
ok := true
opStart := time.Now()
var stats *stats
defer func() {
stats.Record(ok, time.Since(opStart))
}()
row := fmt.Sprintf("row%d", rand.Intn(100)) // operate on 1 of 100 rows
switch rand.Intn(10) {
default:
// read
stats = &reads
_, err := tbl.ReadRow(context.Background(), row, bigtable.RowFilter(bigtable.LatestNFilter(1)))
if err != nil {
log.Printf("Error doing read: %v", err)
ok = false
}
case 0, 1, 2, 3, 4:
// write
stats = &writes
mut := bigtable.NewMutation()
mut.Set("f", "col", bigtable.Now(), bytes.Repeat([]byte("0"), 1<<10)) // 1 KB write
if err := tbl.Apply(context.Background(), row, mut); err != nil {
log.Printf("Error doing mutation: %v", err)
ok = false
}
}
}()
}
wg.Wait()
log.Printf("Reads (%d ok / %d tries):\n%v", reads.ok, reads.tries, newAggregate(reads.ds))
log.Printf("Writes (%d ok / %d tries):\n%v", writes.ok, writes.tries, newAggregate(writes.ds))
}
var allStats int64 // atomic
type stats struct {
mu sync.Mutex
tries, ok int
ds []time.Duration
}
func (s *stats) Record(ok bool, d time.Duration) {
s.mu.Lock()
s.tries++
if ok {
s.ok++
}
s.ds = append(s.ds, d)
s.mu.Unlock()
if n := atomic.AddInt64(&allStats, 1); n%1000 == 0 {
log.Printf("Progress: done %d ops", n)
}
}

View file

@ -0,0 +1,97 @@
package main
import (
"bytes"
"fmt"
"math"
"sort"
"text/tabwriter"
"time"
)
type byDuration []time.Duration
func (data byDuration) Len() int { return len(data) }
func (data byDuration) Swap(i, j int) { data[i], data[j] = data[j], data[i] }
func (data byDuration) Less(i, j int) bool { return data[i] < data[j] }
// quantile returns a value representing the kth of q quantiles.
// May alter the order of data.
func quantile(data []time.Duration, k, q int) (quantile time.Duration, ok bool) {
if len(data) < 1 {
return 0, false
}
if k > q {
return 0, false
}
if k < 0 || q < 1 {
return 0, false
}
sort.Sort(byDuration(data))
if k == 0 {
return data[0], true
}
if k == q {
return data[len(data)-1], true
}
bucketSize := float64(len(data)-1) / float64(q)
i := float64(k) * bucketSize
lower := int(math.Trunc(i))
var upper int
if i > float64(lower) && lower+1 < len(data) {
// If the quantile lies between two elements
upper = lower + 1
} else {
upper = lower
}
weightUpper := i - float64(lower)
weightLower := 1 - weightUpper
return time.Duration(weightLower*float64(data[lower]) + weightUpper*float64(data[upper])), true
}
type aggregate struct {
min, median, max time.Duration
p95, p99 time.Duration // percentiles
}
// newAggregate constructs an aggregate from latencies. Returns nil if latencies does not contain aggregateable data.
func newAggregate(latencies []time.Duration) *aggregate {
var agg aggregate
if len(latencies) == 0 {
return nil
}
var ok bool
if agg.min, ok = quantile(latencies, 0, 2); !ok {
return nil
}
if agg.median, ok = quantile(latencies, 1, 2); !ok {
return nil
}
if agg.max, ok = quantile(latencies, 2, 2); !ok {
return nil
}
if agg.p95, ok = quantile(latencies, 95, 100); !ok {
return nil
}
if agg.p99, ok = quantile(latencies, 99, 100); !ok {
return nil
}
return &agg
}
func (agg *aggregate) String() string {
if agg == nil {
return "no data"
}
var buf bytes.Buffer
tw := tabwriter.NewWriter(&buf, 0, 0, 1, ' ', 0) // one-space padding
fmt.Fprintf(tw, "min:\t%v\nmedian:\t%v\nmax:\t%v\n95th percentile:\t%v\n99th percentile:\t%v\n",
agg.min, agg.median, agg.max, agg.p95, agg.p99)
tw.Flush()
return buf.String()
}

108
vendor/google.golang.org/cloud/bigtable/doc.go generated vendored Normal file
View file

@ -0,0 +1,108 @@
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Package bigtable is an API to Google Cloud Bigtable.
See https://cloud.google.com/bigtable/docs/ for general product documentation.
Setup and Credentials
Use NewClient or NewAdminClient to create a client that can be used to access
the data or admin APIs respectively. Both require credentials that have permission
to access the Cloud Bigtable API.
If your program is run on Google App Engine or Google Compute Engine, using the Application Default Credentials
(https://developers.google.com/accounts/docs/application-default-credentials)
is the simplest option. Those credentials will be used by default when NewClient or NewAdminClient are called.
To use alternate credentials, pass them to NewClient or NewAdminClient using cloud.WithTokenSource.
For instance, you can use service account credentials by visiting
https://cloud.google.com/console/project/MYPROJECT/apiui/credential,
creating a new OAuth "Client ID", storing the JSON key somewhere accessible, and writing
jsonKey, err := ioutil.ReadFile(pathToKeyFile)
...
config, err := google.JWTConfigFromJSON(jsonKey, bigtable.Scope) // or bigtable.AdminScope, etc.
...
client, err := bigtable.NewClient(ctx, project, zone, cluster, cloud.WithTokenSource(config.TokenSource(ctx)))
...
Here, `google` means the golang.org/x/oauth2/google package
and `cloud` means the google.golang.org/cloud package.
Reading
The principal way to read from a Bigtable is to use the ReadRows method on *Table.
A RowRange specifies a contiguous portion of a table. A Filter may be provided through
RowFilter to limit or transform the data that is returned.
tbl := client.Open("mytable")
...
// Read all the rows starting with "com.google.",
// but only fetch the columns in the "links" family.
rr := bigtable.PrefixRange("com.google.")
err := tbl.ReadRows(ctx, rr, func(r Row) bool {
// do something with r
return true // keep going
}, bigtable.RowFilter(bigtable.FamilyFilter("links")))
...
To read a single row, use the ReadRow helper method.
r, err := tbl.ReadRow(ctx, "com.google.cloud") // "com.google.cloud" is the entire row key
...
Writing
This API exposes two distinct forms of writing to a Bigtable: a Mutation and a ReadModifyWrite.
The former expresses idempotent operations.
The latter expresses non-idempotent operations and returns the new values of updated cells.
These operations are performed by creating a Mutation or ReadModifyWrite (with NewMutation or NewReadModifyWrite),
building up one or more operations on that, and then using the Apply or ApplyReadModifyWrite
methods on a Table.
For instance, to set a couple of cells in a table,
tbl := client.Open("mytable")
mut := bigtable.NewMutation()
mut.Set("links", "maps.google.com", bigtable.Now(), []byte("1"))
mut.Set("links", "golang.org", bigtable.Now(), []byte("1"))
err := tbl.Apply(ctx, "com.google.cloud", mut)
...
To increment an encoded value in one cell,
tbl := client.Open("mytable")
rmw := bigtable.NewReadModifyWrite()
rmw.Increment("links", "golang.org", 12) // add 12 to the cell in column "links:golang.org"
r, err := tbl.ApplyReadModifyWrite(ctx, "com.google.cloud", rmw)
...
*/
package bigtable // import "google.golang.org/cloud/bigtable"
// Scope constants for authentication credentials.
// These should be used when using credential creation functions such as oauth.NewServiceAccountFromFile.
const (
// Scope is the OAuth scope for Cloud Bigtable data operations.
Scope = "https://www.googleapis.com/auth/bigtable.data"
// ReadonlyScope is the OAuth scope for Cloud Bigtable read-only data operations.
ReadonlyScope = "https://www.googleapis.com/auth/bigtable.readonly"
// AdminScope is the OAuth scope for Cloud Bigtable table admin operations.
AdminScope = "https://www.googleapis.com/auth/bigtable.admin.table"
// ClusterAdminScope is the OAuth scope for Cloud Bigtable cluster admin operations.
ClusterAdminScope = "https://www.googleapis.com/auth/bigtable.admin.cluster"
)
// clientUserAgent identifies the version of this package.
// It should be bumped upon significant changes only.
const clientUserAgent = "cbt-go/20150727"

156
vendor/google.golang.org/cloud/bigtable/filter.go generated vendored Normal file
View file

@ -0,0 +1,156 @@
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bigtable
import (
"fmt"
"strings"
btdpb "google.golang.org/cloud/bigtable/internal/data_proto"
)
// A Filter represents a row filter.
type Filter interface {
String() string
proto() *btdpb.RowFilter
}
// ChainFilters returns a filter that applies a sequence of filters.
func ChainFilters(sub ...Filter) Filter { return chainFilter{sub} }
type chainFilter struct {
sub []Filter
}
func (cf chainFilter) String() string {
var ss []string
for _, sf := range cf.sub {
ss = append(ss, sf.String())
}
return "(" + strings.Join(ss, " | ") + ")"
}
func (cf chainFilter) proto() *btdpb.RowFilter {
chain := &btdpb.RowFilter_Chain{}
for _, sf := range cf.sub {
chain.Filters = append(chain.Filters, sf.proto())
}
return &btdpb.RowFilter{
Filter: &btdpb.RowFilter_Chain_{chain},
}
}
// InterleaveFilters returns a filter that applies a set of filters in parallel
// and interleaves the results.
func InterleaveFilters(sub ...Filter) Filter { return interleaveFilter{sub} }
type interleaveFilter struct {
sub []Filter
}
func (ilf interleaveFilter) String() string {
var ss []string
for _, sf := range ilf.sub {
ss = append(ss, sf.String())
}
return "(" + strings.Join(ss, " + ") + ")"
}
func (ilf interleaveFilter) proto() *btdpb.RowFilter {
inter := &btdpb.RowFilter_Interleave{}
for _, sf := range ilf.sub {
inter.Filters = append(inter.Filters, sf.proto())
}
return &btdpb.RowFilter{
Filter: &btdpb.RowFilter_Interleave_{inter},
}
}
// RowKeyFilter returns a filter that matches cells from rows whose
// key matches the provided RE2 pattern.
// See https://github.com/google/re2/wiki/Syntax for the accepted syntax.
func RowKeyFilter(pattern string) Filter { return rowKeyFilter(pattern) }
type rowKeyFilter string
func (rkf rowKeyFilter) String() string { return fmt.Sprintf("row(%s)", string(rkf)) }
func (rkf rowKeyFilter) proto() *btdpb.RowFilter {
return &btdpb.RowFilter{Filter: &btdpb.RowFilter_RowKeyRegexFilter{[]byte(rkf)}}
}
// FamilyFilter returns a filter that matches cells whose family name
// matches the provided RE2 pattern.
// See https://github.com/google/re2/wiki/Syntax for the accepted syntax.
func FamilyFilter(pattern string) Filter { return familyFilter(pattern) }
type familyFilter string
func (ff familyFilter) String() string { return fmt.Sprintf("col(%s:)", string(ff)) }
func (ff familyFilter) proto() *btdpb.RowFilter {
return &btdpb.RowFilter{Filter: &btdpb.RowFilter_FamilyNameRegexFilter{string(ff)}}
}
// ColumnFilter returns a filter that matches cells whose column name
// matches the provided RE2 pattern.
// See https://github.com/google/re2/wiki/Syntax for the accepted syntax.
func ColumnFilter(pattern string) Filter { return columnFilter(pattern) }
type columnFilter string
func (cf columnFilter) String() string { return fmt.Sprintf("col(.*:%s)", string(cf)) }
func (cf columnFilter) proto() *btdpb.RowFilter {
return &btdpb.RowFilter{Filter: &btdpb.RowFilter_ColumnQualifierRegexFilter{[]byte(cf)}}
}
// ValueFilter returns a filter that matches cells whose value
// matches the provided RE2 pattern.
// See https://github.com/google/re2/wiki/Syntax for the accepted syntax.
func ValueFilter(pattern string) Filter { return valueFilter(pattern) }
type valueFilter string
func (vf valueFilter) String() string { return fmt.Sprintf("value_match(%s)", string(vf)) }
func (vf valueFilter) proto() *btdpb.RowFilter {
return &btdpb.RowFilter{Filter: &btdpb.RowFilter_ValueRegexFilter{[]byte(vf)}}
}
// LatestNFilter returns a filter that matches the most recent N cells in each column.
func LatestNFilter(n int) Filter { return latestNFilter(n) }
type latestNFilter int32
func (lnf latestNFilter) String() string { return fmt.Sprintf("col(*,%d)", lnf) }
func (lnf latestNFilter) proto() *btdpb.RowFilter {
return &btdpb.RowFilter{Filter: &btdpb.RowFilter_CellsPerColumnLimitFilter{int32(lnf)}}
}
// StripValueFilter returns a filter that replaces each value with the empty string.
func StripValueFilter() Filter { return stripValueFilter{} }
type stripValueFilter struct{}
func (stripValueFilter) String() string { return "strip_value()" }
func (stripValueFilter) proto() *btdpb.RowFilter {
return &btdpb.RowFilter{Filter: &btdpb.RowFilter_StripValueTransformer{true}}
}
// TODO(dsymonds): More filters: cond, col/ts/value range, sampling

131
vendor/google.golang.org/cloud/bigtable/gc.go generated vendored Normal file
View file

@ -0,0 +1,131 @@
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bigtable
import (
"fmt"
"strings"
"time"
durpb "google.golang.org/cloud/bigtable/internal/duration_proto"
bttdpb "google.golang.org/cloud/bigtable/internal/table_data_proto"
)
// A GCPolicy represents a rule that determines which cells are eligible for garbage collection.
type GCPolicy interface {
String() string
proto() *bttdpb.GcRule
}
// IntersectionPolicy returns a GC policy that only applies when all its sub-policies apply.
func IntersectionPolicy(sub ...GCPolicy) GCPolicy { return intersectionPolicy{sub} }
type intersectionPolicy struct {
sub []GCPolicy
}
func (ip intersectionPolicy) String() string {
var ss []string
for _, sp := range ip.sub {
ss = append(ss, sp.String())
}
return "(" + strings.Join(ss, " && ") + ")"
}
func (ip intersectionPolicy) proto() *bttdpb.GcRule {
inter := &bttdpb.GcRule_Intersection{}
for _, sp := range ip.sub {
inter.Rules = append(inter.Rules, sp.proto())
}
return &bttdpb.GcRule{
Rule: &bttdpb.GcRule_Intersection_{inter},
}
}
// UnionPolicy returns a GC policy that applies when any of its sub-policies apply.
func UnionPolicy(sub ...GCPolicy) GCPolicy { return unionPolicy{sub} }
type unionPolicy struct {
sub []GCPolicy
}
func (up unionPolicy) String() string {
var ss []string
for _, sp := range up.sub {
ss = append(ss, sp.String())
}
return "(" + strings.Join(ss, " || ") + ")"
}
func (up unionPolicy) proto() *bttdpb.GcRule {
union := &bttdpb.GcRule_Union{}
for _, sp := range up.sub {
union.Rules = append(union.Rules, sp.proto())
}
return &bttdpb.GcRule{
Rule: &bttdpb.GcRule_Union_{union},
}
}
// MaxVersionsPolicy returns a GC policy that applies to all versions of a cell
// except for the most recent n.
func MaxVersionsPolicy(n int) GCPolicy { return maxVersionsPolicy(n) }
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)}}
}
// MaxAgePolicy returns a GC policy that applies to all cells
// older than the given age.
func MaxAgePolicy(d time.Duration) GCPolicy { return maxAgePolicy(d) }
type maxAgePolicy time.Duration
var units = []struct {
d time.Duration
suffix string
}{
{24 * time.Hour, "d"},
{time.Hour, "h"},
{time.Minute, "m"},
}
func (ma maxAgePolicy) String() string {
d := time.Duration(ma)
for _, u := range units {
if d%u.d == 0 {
return fmt.Sprintf("age() > %d%s", d/u.d, u.suffix)
}
}
return fmt.Sprintf("age() > %d", d/time.Microsecond)
}
func (ma maxAgePolicy) proto() *bttdpb.GcRule {
// This doesn't handle overflows, etc.
// 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{
Seconds: ns / 1e9,
Nanos: int32(ns % 1e9),
}},
}
}

View file

@ -0,0 +1,105 @@
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package cbtrc encapsulates common code for reading .cbtrc files.
package cbtrc
import (
"bufio"
"bytes"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// Config represents a configuration.
type Config struct {
Project, Zone, Cluster string // required
Creds string // optional
}
// RegisterFlags registers a set of standard flags for this config.
// It should be called before flag.Parse.
func (c *Config) RegisterFlags() {
flag.StringVar(&c.Project, "project", c.Project, "project ID")
flag.StringVar(&c.Zone, "zone", c.Zone, "CBT zone")
flag.StringVar(&c.Cluster, "cluster", c.Cluster, "CBT cluster")
flag.StringVar(&c.Creds, "creds", c.Creds, "if set, use application credentials in this file")
}
// CheckFlags checks that the required config values are set.
func (c *Config) CheckFlags() error {
var missing []string
if c.Project == "" {
missing = append(missing, "-project")
}
if c.Zone == "" {
missing = append(missing, "-zone")
}
if c.Cluster == "" {
missing = append(missing, "-cluster")
}
if len(missing) > 0 {
return fmt.Errorf("Missing %s", strings.Join(missing, " and "))
}
return nil
}
// Filename returns the filename consulted for standard configuration.
func Filename() string {
// TODO(dsymonds): Might need tweaking for Windows.
return filepath.Join(os.Getenv("HOME"), ".cbtrc")
}
// Load loads a .cbtrc file.
// If the file is not present, an empty config is returned.
func Load() (*Config, error) {
filename := Filename()
data, err := ioutil.ReadFile(filename)
if err != nil {
// silent fail if the file isn't there
if os.IsNotExist(err) {
return &Config{}, nil
}
return nil, fmt.Errorf("Reading %s: %v", filename, err)
}
c := new(Config)
s := bufio.NewScanner(bytes.NewReader(data))
for s.Scan() {
line := s.Text()
i := strings.Index(line, "=")
if i < 0 {
return nil, fmt.Errorf("Bad line in %s: %q", filename, line)
}
key, val := strings.TrimSpace(line[:i]), strings.TrimSpace(line[i+1:])
switch key {
default:
return nil, fmt.Errorf("Unknown key in %s: %q", filename, key)
case "project":
c.Project = val
case "zone":
c.Zone = val
case "cluster":
c.Cluster = val
case "creds":
c.Creds = val
}
}
return c, s.Err()
}

View file

@ -0,0 +1,119 @@
// Code generated by protoc-gen-go.
// source: google.golang.org/cloud/bigtable/internal/cluster_data_proto/bigtable_cluster_data.proto
// DO NOT EDIT!
/*
Package google_bigtable_admin_cluster_v1 is a generated protocol buffer package.
It is generated from these files:
google.golang.org/cloud/bigtable/internal/cluster_data_proto/bigtable_cluster_data.proto
It has these top-level messages:
Zone
Cluster
*/
package google_bigtable_admin_cluster_v1
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
type StorageType int32
const (
// The storage type used is unspecified.
StorageType_STORAGE_UNSPECIFIED StorageType = 0
// Data will be stored in SSD, providing low and consistent latencies.
StorageType_STORAGE_SSD StorageType = 1
)
var StorageType_name = map[int32]string{
0: "STORAGE_UNSPECIFIED",
1: "STORAGE_SSD",
}
var StorageType_value = map[string]int32{
"STORAGE_UNSPECIFIED": 0,
"STORAGE_SSD": 1,
}
func (x StorageType) String() string {
return proto.EnumName(StorageType_name, int32(x))
}
// Possible states of a zone.
type Zone_Status int32
const (
// The state of the zone is unknown or unspecified.
Zone_UNKNOWN Zone_Status = 0
// The zone is in a good state.
Zone_OK Zone_Status = 1
// The zone is down for planned maintenance.
Zone_PLANNED_MAINTENANCE Zone_Status = 2
// The zone is down for emergency or unplanned maintenance.
Zone_EMERGENCY_MAINENANCE Zone_Status = 3
)
var Zone_Status_name = map[int32]string{
0: "UNKNOWN",
1: "OK",
2: "PLANNED_MAINTENANCE",
3: "EMERGENCY_MAINENANCE",
}
var Zone_Status_value = map[string]int32{
"UNKNOWN": 0,
"OK": 1,
"PLANNED_MAINTENANCE": 2,
"EMERGENCY_MAINENANCE": 3,
}
func (x Zone_Status) String() string {
return proto.EnumName(Zone_Status_name, int32(x))
}
// A physical location in which a particular project can allocate Cloud BigTable
// resources.
type Zone struct {
// A permanent unique identifier for the zone.
// Values are of the form projects/<project>/zones/[a-z][-a-z0-9]*
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// The name of this zone as it appears in UIs.
DisplayName string `protobuf:"bytes,2,opt,name=display_name" json:"display_name,omitempty"`
// The current state of this zone.
Status Zone_Status `protobuf:"varint,3,opt,name=status,enum=google.bigtable.admin.cluster.v1.Zone_Status" json:"status,omitempty"`
}
func (m *Zone) Reset() { *m = Zone{} }
func (m *Zone) String() string { return proto.CompactTextString(m) }
func (*Zone) ProtoMessage() {}
// An isolated set of Cloud BigTable resources on which tables can be hosted.
type Cluster struct {
// A permanent unique identifier for the cluster. For technical reasons, the
// zone in which the cluster resides is included here.
// Values are of the form
// projects/<project>/zones/<zone>/clusters/[a-z][-a-z0-9]*
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// The descriptive name for this cluster as it appears in UIs.
// Must be unique per zone.
DisplayName string `protobuf:"bytes,4,opt,name=display_name" json:"display_name,omitempty"`
// The number of serve nodes allocated to this cluster.
ServeNodes int32 `protobuf:"varint,5,opt,name=serve_nodes" json:"serve_nodes,omitempty"`
// What storage type to use for tables in this cluster. Only configurable at
// cluster creation time. If unspecified, STORAGE_SSD will be used.
DefaultStorageType StorageType `protobuf:"varint,8,opt,name=default_storage_type,enum=google.bigtable.admin.cluster.v1.StorageType" json:"default_storage_type,omitempty"`
}
func (m *Cluster) Reset() { *m = Cluster{} }
func (m *Cluster) String() string { return proto.CompactTextString(m) }
func (*Cluster) ProtoMessage() {}
func init() {
proto.RegisterEnum("google.bigtable.admin.cluster.v1.StorageType", StorageType_name, StorageType_value)
proto.RegisterEnum("google.bigtable.admin.cluster.v1.Zone_Status", Zone_Status_name, Zone_Status_value)
}

View file

@ -0,0 +1,89 @@
// Copyright (c) 2015, Google Inc.
//
// 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.
syntax = "proto3";
package google.bigtable.admin.cluster.v1;
option java_multiple_files = true;
option java_outer_classname = "BigtableClusterDataProto";
option java_package = "com.google.bigtable.admin.cluster.v1";
// A physical location in which a particular project can allocate Cloud BigTable
// resources.
message Zone {
// Possible states of a zone.
enum Status {
// The state of the zone is unknown or unspecified.
UNKNOWN = 0;
// The zone is in a good state.
OK = 1;
// The zone is down for planned maintenance.
PLANNED_MAINTENANCE = 2;
// The zone is down for emergency or unplanned maintenance.
EMERGENCY_MAINENANCE = 3;
}
// A permanent unique identifier for the zone.
// Values are of the form projects/<project>/zones/[a-z][-a-z0-9]*
string name = 1;
// The name of this zone as it appears in UIs.
string display_name = 2;
// The current state of this zone.
Status status = 3;
}
// An isolated set of Cloud BigTable resources on which tables can be hosted.
message Cluster {
// A permanent unique identifier for the cluster. For technical reasons, the
// zone in which the cluster resides is included here.
// Values are of the form
// projects/<project>/zones/<zone>/clusters/[a-z][-a-z0-9]*
string name = 1;
// If this cluster has been deleted, the time at which its backup will
// be irrevocably destroyed. Omitted otherwise.
// This cannot be set directly, only through DeleteCluster.
// The operation currently running on the cluster, if any.
// This cannot be set directly, only through CreateCluster, UpdateCluster,
// or UndeleteCluster. Calls to these methods will be rejected if
// "current_operation" is already set.
// The descriptive name for this cluster as it appears in UIs.
// Must be unique per zone.
string display_name = 4;
// The number of serve nodes allocated to this cluster.
int32 serve_nodes = 5;
// What storage type to use for tables in this cluster. Only configurable at
// cluster creation time. If unspecified, STORAGE_SSD will be used.
StorageType default_storage_type = 8;
}
enum StorageType {
// The storage type used is unspecified.
STORAGE_UNSPECIFIED = 0;
// Data will be stored in SSD, providing low and consistent latencies.
STORAGE_SSD = 1;
}

View file

@ -0,0 +1,331 @@
// Code generated by protoc-gen-go.
// source: google.golang.org/cloud/bigtable/internal/cluster_service_proto/bigtable_cluster_service.proto
// DO NOT EDIT!
package google_bigtable_admin_cluster_v1
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_bigtable_admin_cluster_v11 "google.golang.org/cloud/bigtable/internal/cluster_data_proto"
import google_protobuf "google.golang.org/cloud/bigtable/internal/empty"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// Client API for BigtableClusterService service
type BigtableClusterServiceClient interface {
// Lists the supported zones for the given project.
ListZones(ctx context.Context, in *ListZonesRequest, opts ...grpc.CallOption) (*ListZonesResponse, error)
// Gets information about a particular cluster.
GetCluster(ctx context.Context, in *GetClusterRequest, opts ...grpc.CallOption) (*google_bigtable_admin_cluster_v11.Cluster, error)
// Lists all clusters in the given project, along with any zones for which
// cluster information could not be retrieved.
ListClusters(ctx context.Context, in *ListClustersRequest, opts ...grpc.CallOption) (*ListClustersResponse, error)
// Creates a cluster and begins preparing it to begin serving. The returned
// cluster embeds as its "current_operation" a long-running operation which
// can be used to track the progress of turning up the new cluster.
// Immediately upon completion of this request:
// * The cluster will be readable via the API, with all requested attributes
// but no allocated resources.
// Until completion of the embedded operation:
// * Cancelling the operation will render the cluster immediately unreadable
// via the API.
// * All other attempts to modify or delete the cluster will be rejected.
// Upon completion of the embedded operation:
// * Billing for all successfully-allocated resources will begin (some types
// may have lower than the requested levels).
// * New tables can be created in the cluster.
// * The cluster's allocated resource levels will be readable via the API.
// The embedded operation's "metadata" field type is
// [CreateClusterMetadata][google.bigtable.admin.cluster.v1.CreateClusterMetadata] The embedded operation's "response" field type is
// [Cluster][google.bigtable.admin.cluster.v1.Cluster], if successful.
CreateCluster(ctx context.Context, in *CreateClusterRequest, opts ...grpc.CallOption) (*google_bigtable_admin_cluster_v11.Cluster, error)
// Updates a cluster, and begins allocating or releasing resources as
// requested. The returned cluster embeds as its "current_operation" a
// long-running operation which can be used to track the progress of updating
// the cluster.
// Immediately upon completion of this request:
// * For resource types where a decrease in the cluster's allocation has been
// requested, billing will be based on the newly-requested level.
// Until completion of the embedded operation:
// * Cancelling the operation will set its metadata's "cancelled_at_time",
// and begin restoring resources to their pre-request values. The operation
// is guaranteed to succeed at undoing all resource changes, after which
// point it will terminate with a CANCELLED status.
// * All other attempts to modify or delete the cluster will be rejected.
// * Reading the cluster via the API will continue to give the pre-request
// resource levels.
// Upon completion of the embedded operation:
// * Billing will begin for all successfully-allocated resources (some types
// may have lower than the requested levels).
// * All newly-reserved resources will be available for serving the cluster's
// tables.
// * The cluster's new resource levels will be readable via the API.
// [UpdateClusterMetadata][google.bigtable.admin.cluster.v1.UpdateClusterMetadata] The embedded operation's "response" field type is
// [Cluster][google.bigtable.admin.cluster.v1.Cluster], if successful.
UpdateCluster(ctx context.Context, in *google_bigtable_admin_cluster_v11.Cluster, opts ...grpc.CallOption) (*google_bigtable_admin_cluster_v11.Cluster, error)
// Marks a cluster and all of its tables for permanent deletion in 7 days.
// Immediately upon completion of the request:
// * Billing will cease for all of the cluster's reserved resources.
// * The cluster's "delete_time" field will be set 7 days in the future.
// Soon afterward:
// * All tables within the cluster will become unavailable.
// Prior to the cluster's "delete_time":
// * The cluster can be recovered with a call to UndeleteCluster.
// * All other attempts to modify or delete the cluster will be rejected.
// At the cluster's "delete_time":
// * The cluster and *all of its tables* will immediately and irrevocably
// disappear from the API, and their data will be permanently deleted.
DeleteCluster(ctx context.Context, in *DeleteClusterRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error)
}
type bigtableClusterServiceClient struct {
cc *grpc.ClientConn
}
func NewBigtableClusterServiceClient(cc *grpc.ClientConn) BigtableClusterServiceClient {
return &bigtableClusterServiceClient{cc}
}
func (c *bigtableClusterServiceClient) ListZones(ctx context.Context, in *ListZonesRequest, opts ...grpc.CallOption) (*ListZonesResponse, error) {
out := new(ListZonesResponse)
err := grpc.Invoke(ctx, "/google.bigtable.admin.cluster.v1.BigtableClusterService/ListZones", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableClusterServiceClient) GetCluster(ctx context.Context, in *GetClusterRequest, opts ...grpc.CallOption) (*google_bigtable_admin_cluster_v11.Cluster, error) {
out := new(google_bigtable_admin_cluster_v11.Cluster)
err := grpc.Invoke(ctx, "/google.bigtable.admin.cluster.v1.BigtableClusterService/GetCluster", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableClusterServiceClient) ListClusters(ctx context.Context, in *ListClustersRequest, opts ...grpc.CallOption) (*ListClustersResponse, error) {
out := new(ListClustersResponse)
err := grpc.Invoke(ctx, "/google.bigtable.admin.cluster.v1.BigtableClusterService/ListClusters", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableClusterServiceClient) CreateCluster(ctx context.Context, in *CreateClusterRequest, opts ...grpc.CallOption) (*google_bigtable_admin_cluster_v11.Cluster, error) {
out := new(google_bigtable_admin_cluster_v11.Cluster)
err := grpc.Invoke(ctx, "/google.bigtable.admin.cluster.v1.BigtableClusterService/CreateCluster", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableClusterServiceClient) UpdateCluster(ctx context.Context, in *google_bigtable_admin_cluster_v11.Cluster, opts ...grpc.CallOption) (*google_bigtable_admin_cluster_v11.Cluster, error) {
out := new(google_bigtable_admin_cluster_v11.Cluster)
err := grpc.Invoke(ctx, "/google.bigtable.admin.cluster.v1.BigtableClusterService/UpdateCluster", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableClusterServiceClient) DeleteCluster(ctx context.Context, in *DeleteClusterRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) {
out := new(google_protobuf.Empty)
err := grpc.Invoke(ctx, "/google.bigtable.admin.cluster.v1.BigtableClusterService/DeleteCluster", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for BigtableClusterService service
type BigtableClusterServiceServer interface {
// Lists the supported zones for the given project.
ListZones(context.Context, *ListZonesRequest) (*ListZonesResponse, error)
// Gets information about a particular cluster.
GetCluster(context.Context, *GetClusterRequest) (*google_bigtable_admin_cluster_v11.Cluster, error)
// Lists all clusters in the given project, along with any zones for which
// cluster information could not be retrieved.
ListClusters(context.Context, *ListClustersRequest) (*ListClustersResponse, error)
// Creates a cluster and begins preparing it to begin serving. The returned
// cluster embeds as its "current_operation" a long-running operation which
// can be used to track the progress of turning up the new cluster.
// Immediately upon completion of this request:
// * The cluster will be readable via the API, with all requested attributes
// but no allocated resources.
// Until completion of the embedded operation:
// * Cancelling the operation will render the cluster immediately unreadable
// via the API.
// * All other attempts to modify or delete the cluster will be rejected.
// Upon completion of the embedded operation:
// * Billing for all successfully-allocated resources will begin (some types
// may have lower than the requested levels).
// * New tables can be created in the cluster.
// * The cluster's allocated resource levels will be readable via the API.
// The embedded operation's "metadata" field type is
// [CreateClusterMetadata][google.bigtable.admin.cluster.v1.CreateClusterMetadata] The embedded operation's "response" field type is
// [Cluster][google.bigtable.admin.cluster.v1.Cluster], if successful.
CreateCluster(context.Context, *CreateClusterRequest) (*google_bigtable_admin_cluster_v11.Cluster, error)
// Updates a cluster, and begins allocating or releasing resources as
// requested. The returned cluster embeds as its "current_operation" a
// long-running operation which can be used to track the progress of updating
// the cluster.
// Immediately upon completion of this request:
// * For resource types where a decrease in the cluster's allocation has been
// requested, billing will be based on the newly-requested level.
// Until completion of the embedded operation:
// * Cancelling the operation will set its metadata's "cancelled_at_time",
// and begin restoring resources to their pre-request values. The operation
// is guaranteed to succeed at undoing all resource changes, after which
// point it will terminate with a CANCELLED status.
// * All other attempts to modify or delete the cluster will be rejected.
// * Reading the cluster via the API will continue to give the pre-request
// resource levels.
// Upon completion of the embedded operation:
// * Billing will begin for all successfully-allocated resources (some types
// may have lower than the requested levels).
// * All newly-reserved resources will be available for serving the cluster's
// tables.
// * The cluster's new resource levels will be readable via the API.
// [UpdateClusterMetadata][google.bigtable.admin.cluster.v1.UpdateClusterMetadata] The embedded operation's "response" field type is
// [Cluster][google.bigtable.admin.cluster.v1.Cluster], if successful.
UpdateCluster(context.Context, *google_bigtable_admin_cluster_v11.Cluster) (*google_bigtable_admin_cluster_v11.Cluster, error)
// Marks a cluster and all of its tables for permanent deletion in 7 days.
// Immediately upon completion of the request:
// * Billing will cease for all of the cluster's reserved resources.
// * The cluster's "delete_time" field will be set 7 days in the future.
// Soon afterward:
// * All tables within the cluster will become unavailable.
// Prior to the cluster's "delete_time":
// * The cluster can be recovered with a call to UndeleteCluster.
// * All other attempts to modify or delete the cluster will be rejected.
// At the cluster's "delete_time":
// * The cluster and *all of its tables* will immediately and irrevocably
// disappear from the API, and their data will be permanently deleted.
DeleteCluster(context.Context, *DeleteClusterRequest) (*google_protobuf.Empty, error)
}
func RegisterBigtableClusterServiceServer(s *grpc.Server, srv BigtableClusterServiceServer) {
s.RegisterService(&_BigtableClusterService_serviceDesc, srv)
}
func _BigtableClusterService_ListZones_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(ListZonesRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableClusterServiceServer).ListZones(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableClusterService_GetCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(GetClusterRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableClusterServiceServer).GetCluster(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableClusterService_ListClusters_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(ListClustersRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableClusterServiceServer).ListClusters(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableClusterService_CreateCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(CreateClusterRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableClusterServiceServer).CreateCluster(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableClusterService_UpdateCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(google_bigtable_admin_cluster_v11.Cluster)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableClusterServiceServer).UpdateCluster(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableClusterService_DeleteCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(DeleteClusterRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableClusterServiceServer).DeleteCluster(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
var _BigtableClusterService_serviceDesc = grpc.ServiceDesc{
ServiceName: "google.bigtable.admin.cluster.v1.BigtableClusterService",
HandlerType: (*BigtableClusterServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ListZones",
Handler: _BigtableClusterService_ListZones_Handler,
},
{
MethodName: "GetCluster",
Handler: _BigtableClusterService_GetCluster_Handler,
},
{
MethodName: "ListClusters",
Handler: _BigtableClusterService_ListClusters_Handler,
},
{
MethodName: "CreateCluster",
Handler: _BigtableClusterService_CreateCluster_Handler,
},
{
MethodName: "UpdateCluster",
Handler: _BigtableClusterService_UpdateCluster_Handler,
},
{
MethodName: "DeleteCluster",
Handler: _BigtableClusterService_DeleteCluster_Handler,
},
},
Streams: []grpc.StreamDesc{},
}

View file

@ -0,0 +1,118 @@
// Copyright (c) 2015, Google Inc.
//
// 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.
syntax = "proto3";
package google.bigtable.admin.cluster.v1;
import "google.golang.org/cloud/bigtable/internal/cluster_data_proto/bigtable_cluster_data.proto";
import "google.golang.org/cloud/bigtable/internal/cluster_service_proto/bigtable_cluster_service_messages.proto";
import "google.golang.org/cloud/bigtable/internal/empty/empty.proto";
option java_multiple_files = true;
option java_outer_classname = "BigtableClusterServicesProto";
option java_package = "com.google.bigtable.admin.cluster.v1";
// Service for managing zonal Cloud Bigtable resources.
service BigtableClusterService {
// Lists the supported zones for the given project.
rpc ListZones(ListZonesRequest) returns (ListZonesResponse) {
}
// Gets information about a particular cluster.
rpc GetCluster(GetClusterRequest) returns (Cluster) {
}
// Lists all clusters in the given project, along with any zones for which
// cluster information could not be retrieved.
rpc ListClusters(ListClustersRequest) returns (ListClustersResponse) {
}
// Creates a cluster and begins preparing it to begin serving. The returned
// cluster embeds as its "current_operation" a long-running operation which
// can be used to track the progress of turning up the new cluster.
// Immediately upon completion of this request:
// * The cluster will be readable via the API, with all requested attributes
// but no allocated resources.
// Until completion of the embedded operation:
// * Cancelling the operation will render the cluster immediately unreadable
// via the API.
// * All other attempts to modify or delete the cluster will be rejected.
// Upon completion of the embedded operation:
// * Billing for all successfully-allocated resources will begin (some types
// may have lower than the requested levels).
// * New tables can be created in the cluster.
// * The cluster's allocated resource levels will be readable via the API.
// The embedded operation's "metadata" field type is
// [CreateClusterMetadata][google.bigtable.admin.cluster.v1.CreateClusterMetadata] The embedded operation's "response" field type is
// [Cluster][google.bigtable.admin.cluster.v1.Cluster], if successful.
rpc CreateCluster(CreateClusterRequest) returns (Cluster) {
}
// Updates a cluster, and begins allocating or releasing resources as
// requested. The returned cluster embeds as its "current_operation" a
// long-running operation which can be used to track the progress of updating
// the cluster.
// Immediately upon completion of this request:
// * For resource types where a decrease in the cluster's allocation has been
// requested, billing will be based on the newly-requested level.
// Until completion of the embedded operation:
// * Cancelling the operation will set its metadata's "cancelled_at_time",
// and begin restoring resources to their pre-request values. The operation
// is guaranteed to succeed at undoing all resource changes, after which
// point it will terminate with a CANCELLED status.
// * All other attempts to modify or delete the cluster will be rejected.
// * Reading the cluster via the API will continue to give the pre-request
// resource levels.
// Upon completion of the embedded operation:
// * Billing will begin for all successfully-allocated resources (some types
// may have lower than the requested levels).
// * All newly-reserved resources will be available for serving the cluster's
// tables.
// * The cluster's new resource levels will be readable via the API.
// [UpdateClusterMetadata][google.bigtable.admin.cluster.v1.UpdateClusterMetadata] The embedded operation's "response" field type is
// [Cluster][google.bigtable.admin.cluster.v1.Cluster], if successful.
rpc UpdateCluster(Cluster) returns (Cluster) {
}
// Marks a cluster and all of its tables for permanent deletion in 7 days.
// Immediately upon completion of the request:
// * Billing will cease for all of the cluster's reserved resources.
// * The cluster's "delete_time" field will be set 7 days in the future.
// Soon afterward:
// * All tables within the cluster will become unavailable.
// Prior to the cluster's "delete_time":
// * The cluster can be recovered with a call to UndeleteCluster.
// * All other attempts to modify or delete the cluster will be rejected.
// At the cluster's "delete_time":
// * The cluster and *all of its tables* will immediately and irrevocably
// disappear from the API, and their data will be permanently deleted.
rpc DeleteCluster(DeleteClusterRequest) returns (google.protobuf.Empty) {
}
// Cancels the scheduled deletion of an cluster and begins preparing it to
// resume serving. The returned operation will also be embedded as the
// cluster's "current_operation".
// Immediately upon completion of this request:
// * The cluster's "delete_time" field will be unset, protecting it from
// automatic deletion.
// Until completion of the returned operation:
// * The operation cannot be cancelled.
// Upon completion of the returned operation:
// * Billing for the cluster's resources will resume.
// * All tables within the cluster will be available.
// [UndeleteClusterMetadata][google.bigtable.admin.cluster.v1.UndeleteClusterMetadata] The embedded operation's "response" field type is
// [Cluster][google.bigtable.admin.cluster.v1.Cluster], if successful.
}

View file

@ -0,0 +1,205 @@
// Code generated by protoc-gen-go.
// source: google.golang.org/cloud/bigtable/internal/cluster_service_proto/bigtable_cluster_service_messages.proto
// DO NOT EDIT!
/*
Package google_bigtable_admin_cluster_v1 is a generated protocol buffer package.
It is generated from these files:
google.golang.org/cloud/bigtable/internal/cluster_service_proto/bigtable_cluster_service_messages.proto
google.golang.org/cloud/bigtable/internal/cluster_service_proto/bigtable_cluster_service.proto
It has these top-level messages:
ListZonesRequest
ListZonesResponse
GetClusterRequest
ListClustersRequest
ListClustersResponse
CreateClusterRequest
CreateClusterMetadata
UpdateClusterMetadata
DeleteClusterRequest
UndeleteClusterRequest
UndeleteClusterMetadata
*/
package google_bigtable_admin_cluster_v1
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_bigtable_admin_cluster_v11 "google.golang.org/cloud/bigtable/internal/cluster_data_proto"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// Request message for BigtableClusterService.ListZones.
type ListZonesRequest struct {
// The unique name of the project for which a list of supported zones is
// requested.
// Values are of the form projects/<project>
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *ListZonesRequest) Reset() { *m = ListZonesRequest{} }
func (m *ListZonesRequest) String() string { return proto.CompactTextString(m) }
func (*ListZonesRequest) ProtoMessage() {}
// Response message for BigtableClusterService.ListZones.
type ListZonesResponse struct {
// The list of requested zones.
Zones []*google_bigtable_admin_cluster_v11.Zone `protobuf:"bytes,1,rep,name=zones" json:"zones,omitempty"`
}
func (m *ListZonesResponse) Reset() { *m = ListZonesResponse{} }
func (m *ListZonesResponse) String() string { return proto.CompactTextString(m) }
func (*ListZonesResponse) ProtoMessage() {}
func (m *ListZonesResponse) GetZones() []*google_bigtable_admin_cluster_v11.Zone {
if m != nil {
return m.Zones
}
return nil
}
// Request message for BigtableClusterService.GetCluster.
type GetClusterRequest struct {
// The unique name of the requested cluster.
// Values are of the form projects/<project>/zones/<zone>/clusters/<cluster>
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *GetClusterRequest) Reset() { *m = GetClusterRequest{} }
func (m *GetClusterRequest) String() string { return proto.CompactTextString(m) }
func (*GetClusterRequest) ProtoMessage() {}
// Request message for BigtableClusterService.ListClusters.
type ListClustersRequest struct {
// The unique name of the project for which a list of clusters is requested.
// Values are of the form projects/<project>
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *ListClustersRequest) Reset() { *m = ListClustersRequest{} }
func (m *ListClustersRequest) String() string { return proto.CompactTextString(m) }
func (*ListClustersRequest) ProtoMessage() {}
// Response message for BigtableClusterService.ListClusters.
type ListClustersResponse struct {
// The list of requested Clusters.
Clusters []*google_bigtable_admin_cluster_v11.Cluster `protobuf:"bytes,1,rep,name=clusters" json:"clusters,omitempty"`
// The zones for which clusters could not be retrieved.
FailedZones []*google_bigtable_admin_cluster_v11.Zone `protobuf:"bytes,2,rep,name=failed_zones" json:"failed_zones,omitempty"`
}
func (m *ListClustersResponse) Reset() { *m = ListClustersResponse{} }
func (m *ListClustersResponse) String() string { return proto.CompactTextString(m) }
func (*ListClustersResponse) ProtoMessage() {}
func (m *ListClustersResponse) GetClusters() []*google_bigtable_admin_cluster_v11.Cluster {
if m != nil {
return m.Clusters
}
return nil
}
func (m *ListClustersResponse) GetFailedZones() []*google_bigtable_admin_cluster_v11.Zone {
if m != nil {
return m.FailedZones
}
return nil
}
// Request message for BigtableClusterService.CreateCluster.
type CreateClusterRequest struct {
// The unique name of the zone in which to create the cluster.
// Values are of the form projects/<project>/zones/<zone>
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// The id to be used when referring to the new cluster within its zone,
// e.g. just the "test-cluster" section of the full name
// "projects/<project>/zones/<zone>/clusters/test-cluster".
ClusterId string `protobuf:"bytes,2,opt,name=cluster_id" json:"cluster_id,omitempty"`
// The cluster to create.
// The "name", "delete_time", and "current_operation" fields must be left
// blank.
Cluster *google_bigtable_admin_cluster_v11.Cluster `protobuf:"bytes,3,opt,name=cluster" json:"cluster,omitempty"`
}
func (m *CreateClusterRequest) Reset() { *m = CreateClusterRequest{} }
func (m *CreateClusterRequest) String() string { return proto.CompactTextString(m) }
func (*CreateClusterRequest) ProtoMessage() {}
func (m *CreateClusterRequest) GetCluster() *google_bigtable_admin_cluster_v11.Cluster {
if m != nil {
return m.Cluster
}
return nil
}
// Metadata type for the operation returned by
// BigtableClusterService.CreateCluster.
type CreateClusterMetadata struct {
// The request which prompted the creation of this operation.
OriginalRequest *CreateClusterRequest `protobuf:"bytes,1,opt,name=original_request" json:"original_request,omitempty"`
}
func (m *CreateClusterMetadata) Reset() { *m = CreateClusterMetadata{} }
func (m *CreateClusterMetadata) String() string { return proto.CompactTextString(m) }
func (*CreateClusterMetadata) ProtoMessage() {}
func (m *CreateClusterMetadata) GetOriginalRequest() *CreateClusterRequest {
if m != nil {
return m.OriginalRequest
}
return nil
}
// Metadata type for the operation returned by
// BigtableClusterService.UpdateCluster.
type UpdateClusterMetadata struct {
// The request which prompted the creation of this operation.
OriginalRequest *google_bigtable_admin_cluster_v11.Cluster `protobuf:"bytes,1,opt,name=original_request" json:"original_request,omitempty"`
}
func (m *UpdateClusterMetadata) Reset() { *m = UpdateClusterMetadata{} }
func (m *UpdateClusterMetadata) String() string { return proto.CompactTextString(m) }
func (*UpdateClusterMetadata) ProtoMessage() {}
func (m *UpdateClusterMetadata) GetOriginalRequest() *google_bigtable_admin_cluster_v11.Cluster {
if m != nil {
return m.OriginalRequest
}
return nil
}
// Request message for BigtableClusterService.DeleteCluster.
type DeleteClusterRequest struct {
// The unique name of the cluster to be deleted.
// Values are of the form projects/<project>/zones/<zone>/clusters/<cluster>
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *DeleteClusterRequest) Reset() { *m = DeleteClusterRequest{} }
func (m *DeleteClusterRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteClusterRequest) ProtoMessage() {}
// Request message for BigtableClusterService.UndeleteCluster.
type UndeleteClusterRequest struct {
// The unique name of the cluster to be un-deleted.
// Values are of the form projects/<project>/zones/<zone>/clusters/<cluster>
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *UndeleteClusterRequest) Reset() { *m = UndeleteClusterRequest{} }
func (m *UndeleteClusterRequest) String() string { return proto.CompactTextString(m) }
func (*UndeleteClusterRequest) ProtoMessage() {}
// Metadata type for the operation returned by
// BigtableClusterService.UndeleteCluster.
type UndeleteClusterMetadata struct {
}
func (m *UndeleteClusterMetadata) Reset() { *m = UndeleteClusterMetadata{} }
func (m *UndeleteClusterMetadata) String() string { return proto.CompactTextString(m) }
func (*UndeleteClusterMetadata) ProtoMessage() {}

View file

@ -0,0 +1,126 @@
// Copyright (c) 2015, Google Inc.
//
// 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.
syntax = "proto3";
package google.bigtable.admin.cluster.v1;
import "google.golang.org/cloud/bigtable/internal/cluster_data_proto/bigtable_cluster_data.proto";
option java_multiple_files = true;
option java_outer_classname = "BigtableClusterServiceMessagesProto";
option java_package = "com.google.bigtable.admin.cluster.v1";
// Request message for BigtableClusterService.ListZones.
message ListZonesRequest {
// The unique name of the project for which a list of supported zones is
// requested.
// Values are of the form projects/<project>
string name = 1;
}
// Response message for BigtableClusterService.ListZones.
message ListZonesResponse {
// The list of requested zones.
repeated Zone zones = 1;
}
// Request message for BigtableClusterService.GetCluster.
message GetClusterRequest {
// The unique name of the requested cluster.
// Values are of the form projects/<project>/zones/<zone>/clusters/<cluster>
string name = 1;
}
// Request message for BigtableClusterService.ListClusters.
message ListClustersRequest {
// The unique name of the project for which a list of clusters is requested.
// Values are of the form projects/<project>
string name = 1;
}
// Response message for BigtableClusterService.ListClusters.
message ListClustersResponse {
// The list of requested Clusters.
repeated Cluster clusters = 1;
// The zones for which clusters could not be retrieved.
repeated Zone failed_zones = 2;
}
// Request message for BigtableClusterService.CreateCluster.
message CreateClusterRequest {
// The unique name of the zone in which to create the cluster.
// Values are of the form projects/<project>/zones/<zone>
string name = 1;
// The id to be used when referring to the new cluster within its zone,
// e.g. just the "test-cluster" section of the full name
// "projects/<project>/zones/<zone>/clusters/test-cluster".
string cluster_id = 2;
// The cluster to create.
// The "name", "delete_time", and "current_operation" fields must be left
// blank.
Cluster cluster = 3;
}
// Metadata type for the operation returned by
// BigtableClusterService.CreateCluster.
message CreateClusterMetadata {
// The request which prompted the creation of this operation.
CreateClusterRequest original_request = 1;
// The time at which original_request was received.
// The time at which this operation failed or was completed successfully.
}
// Metadata type for the operation returned by
// BigtableClusterService.UpdateCluster.
message UpdateClusterMetadata {
// The request which prompted the creation of this operation.
Cluster original_request = 1;
// The time at which original_request was received.
// The time at which this operation was cancelled. If set, this operation is
// in the process of undoing itself (which is guaranteed to succeed) and
// cannot be cancelled again.
// The time at which this operation failed or was completed successfully.
}
// Request message for BigtableClusterService.DeleteCluster.
message DeleteClusterRequest {
// The unique name of the cluster to be deleted.
// Values are of the form projects/<project>/zones/<zone>/clusters/<cluster>
string name = 1;
}
// Request message for BigtableClusterService.UndeleteCluster.
message UndeleteClusterRequest {
// The unique name of the cluster to be un-deleted.
// Values are of the form projects/<project>/zones/<zone>/clusters/<cluster>
string name = 1;
}
// Metadata type for the operation returned by
// BigtableClusterService.UndeleteCluster.
message UndeleteClusterMetadata {
// The time at which the original request was received.
// The time at which this operation failed or was completed successfully.
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,500 @@
// Copyright (c) 2015, Google Inc.
//
// 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.
syntax = "proto3";
package google.bigtable.v1;
option java_multiple_files = true;
option java_outer_classname = "BigtableDataProto";
option java_package = "com.google.bigtable.v1";
// Specifies the complete (requested) contents of a single row of a table.
// Rows which exceed 256MiB in size cannot be read in full.
message Row {
// The unique key which identifies this row within its table. This is the same
// key that's used to identify the row in, for example, a MutateRowRequest.
// May contain any non-empty byte string up to 4KiB in length.
bytes key = 1;
// May be empty, but only if the entire row is empty.
// The mutual ordering of column families is not specified.
repeated Family families = 2;
}
// Specifies (some of) the contents of a single row/column family of a table.
message Family {
// The unique key which identifies this family within its row. This is the
// same key that's used to identify the family in, for example, a RowFilter
// which sets its "family_name_regex_filter" field.
// Must match [-_.a-zA-Z0-9]+, except that AggregatingRowProcessors may
// produce cells in a sentinel family with an empty name.
// Must be no greater than 64 characters in length.
string name = 1;
// Must not be empty. Sorted in order of increasing "qualifier".
repeated Column columns = 2;
}
// Specifies (some of) the contents of a single row/column of a table.
message Column {
// The unique key which identifies this column within its family. This is the
// same key that's used to identify the column in, for example, a RowFilter
// which sets its "column_qualifier_regex_filter" field.
// May contain any byte string, including the empty string, up to 16kiB in
// length.
bytes qualifier = 1;
// Must not be empty. Sorted in order of decreasing "timestamp_micros".
repeated Cell cells = 2;
}
// Specifies (some of) the contents of a single row/column/timestamp of a table.
message Cell {
// The cell's stored timestamp, which also uniquely identifies it within
// its column.
// Values are always expressed in microseconds, but individual tables may set
// a coarser "granularity" to further restrict the allowed values. For
// example, a table which specifies millisecond granularity will only allow
// values of "timestamp_micros" which are multiples of 1000.
int64 timestamp_micros = 1;
// The value stored in the cell.
// May contain any byte string, including the empty string, up to 100MiB in
// length.
bytes value = 2;
// Labels applied to the cell by a [RowFilter][google.bigtable.v1.RowFilter].
repeated string labels = 3;
}
// Specifies a contiguous range of rows.
message RowRange {
// Inclusive lower bound. If left empty, interpreted as the empty string.
bytes start_key = 2;
// Exclusive upper bound. If left empty, interpreted as infinity.
bytes end_key = 3;
}
// Specifies a contiguous range of columns within a single column family.
// The range spans from <column_family>:<start_qualifier> to
// <column_family>:<end_qualifier>, where both bounds can be either inclusive or
// exclusive.
message ColumnRange {
// The name of the column family within which this range falls.
string family_name = 1;
// The column qualifier at which to start the range (within 'column_family').
// If neither field is set, interpreted as the empty string, inclusive.
oneof start_qualifier {
// Used when giving an inclusive lower bound for the range.
bytes start_qualifier_inclusive = 2;
// Used when giving an exclusive lower bound for the range.
bytes start_qualifier_exclusive = 3;
}
// The column qualifier at which to end the range (within 'column_family').
// If neither field is set, interpreted as the infinite string, exclusive.
oneof end_qualifier {
// Used when giving an inclusive upper bound for the range.
bytes end_qualifier_inclusive = 4;
// Used when giving an exclusive upper bound for the range.
bytes end_qualifier_exclusive = 5;
}
}
// Specified a contiguous range of microsecond timestamps.
message TimestampRange {
// Inclusive lower bound. If left empty, interpreted as 0.
int64 start_timestamp_micros = 1;
// Exclusive upper bound. If left empty, interpreted as infinity.
int64 end_timestamp_micros = 2;
}
// Specifies a contiguous range of raw byte values.
message ValueRange {
// The value at which to start the range.
// If neither field is set, interpreted as the empty string, inclusive.
oneof start_value {
// Used when giving an inclusive lower bound for the range.
bytes start_value_inclusive = 1;
// Used when giving an exclusive lower bound for the range.
bytes start_value_exclusive = 2;
}
// The value at which to end the range.
// If neither field is set, interpreted as the infinite string, exclusive.
oneof end_value {
// Used when giving an inclusive upper bound for the range.
bytes end_value_inclusive = 3;
// Used when giving an exclusive upper bound for the range.
bytes end_value_exclusive = 4;
}
}
// Takes a row as input and produces an alternate view of the row based on
// specified rules. For example, a RowFilter might trim down a row to include
// just the cells from columns matching a given regular expression, or might
// return all the cells of a row but not their values. More complicated filters
// can be composed out of these components to express requests such as, "within
// every column of a particular family, give just the two most recent cells
// which are older than timestamp X."
//
// There are two broad categories of RowFilters (true filters and transformers),
// as well as two ways to compose simple filters into more complex ones
// (chains and interleaves). They work as follows:
//
// * True filters alter the input row by excluding some of its cells wholesale
// from the output row. An example of a true filter is the "value_regex_filter",
// which excludes cells whose values don't match the specified pattern. All
// regex true filters use RE2 syntax (https://github.com/google/re2/wiki/Syntax)
// in raw byte mode (RE2::Latin1), and are evaluated as full matches. An
// important point to keep in mind is that RE2(.) is equivalent by default to
// RE2([^\n]), meaning that it does not match newlines. When attempting to match
// an arbitrary byte, you should therefore use the escape sequence '\C', which
// may need to be further escaped as '\\C' in your client language.
//
// * Transformers alter the input row by changing the values of some of its
// cells in the output, without excluding them completely. Currently, the only
// supported transformer is the "strip_value_transformer", which replaces every
// cell's value with the empty string.
//
// * Chains and interleaves are described in more detail in the
// RowFilter.Chain and RowFilter.Interleave documentation.
//
// The total serialized size of a RowFilter message must not
// exceed 4096 bytes, and RowFilters may not be nested within each other
// (in Chains or Interleaves) to a depth of more than 20.
message RowFilter {
// A RowFilter which sends rows through several RowFilters in sequence.
message Chain {
// The elements of "filters" are chained together to process the input row:
// in row -> f(0) -> intermediate row -> f(1) -> ... -> f(N) -> out row
// The full chain is executed atomically.
repeated RowFilter filters = 1;
}
// A RowFilter which sends each row to each of several component
// RowFilters and interleaves the results.
message Interleave {
// The elements of "filters" all process a copy of the input row, and the
// results are pooled, sorted, and combined into a single output row.
// If multiple cells are produced with the same column and timestamp,
// they will all appear in the output row in an unspecified mutual order.
// Consider the following example, with three filters:
//
// input row
// |
// -----------------------------------------------------
// | | |
// f(0) f(1) f(2)
// | | |
// 1: foo,bar,10,x foo,bar,10,z far,bar,7,a
// 2: foo,blah,11,z far,blah,5,x far,blah,5,x
// | | |
// -----------------------------------------------------
// |
// 1: foo,bar,10,z // could have switched with #2
// 2: foo,bar,10,x // could have switched with #1
// 3: foo,blah,11,z
// 4: far,bar,7,a
// 5: far,blah,5,x // identical to #6
// 6: far,blah,5,x // identical to #5
// All interleaved filters are executed atomically.
repeated RowFilter filters = 1;
}
// A RowFilter which evaluates one of two possible RowFilters, depending on
// whether or not a predicate RowFilter outputs any cells from the input row.
//
// IMPORTANT NOTE: The predicate filter does not execute atomically with the
// true and false filters, which may lead to inconsistent or unexpected
// results. Additionally, Condition filters have poor performance, especially
// when filters are set for the false condition.
message Condition {
// If "predicate_filter" outputs any cells, then "true_filter" will be
// evaluated on the input row. Otherwise, "false_filter" will be evaluated.
RowFilter predicate_filter = 1;
// The filter to apply to the input row if "predicate_filter" returns any
// results. If not provided, no results will be returned in the true case.
RowFilter true_filter = 2;
// The filter to apply to the input row if "predicate_filter" does not
// return any results. If not provided, no results will be returned in the
// false case.
RowFilter false_filter = 3;
}
// Which of the possible RowFilter types to apply. If none are set, this
// RowFilter returns all cells in the input row.
oneof filter {
// Applies several RowFilters to the data in sequence, progressively
// narrowing the results.
Chain chain = 1;
// Applies several RowFilters to the data in parallel and combines the
// results.
Interleave interleave = 2;
// Applies one of two possible RowFilters to the data based on the output of
// a predicate RowFilter.
Condition condition = 3;
// ADVANCED USE ONLY.
// Hook for introspection into the RowFilter. Outputs all cells directly to
// the output of the read rather than to any parent filter. Consider the
// following example:
//
// Chain(
// FamilyRegex("A"),
// Interleave(
// All(),
// Chain(Label("foo"), Sink())
// ),
// QualifierRegex("B")
// )
//
// A,A,1,w
// A,B,2,x
// B,B,4,z
// |
// FamilyRegex("A")
// |
// A,A,1,w
// A,B,2,x
// |
// +------------+-------------+
// | |
// All() Label(foo)
// | |
// A,A,1,w A,A,1,w,labels:[foo]
// A,B,2,x A,B,2,x,labels:[foo]
// | |
// | Sink() --------------+
// | | |
// +------------+ x------+ A,A,1,w,labels:[foo]
// | A,B,2,x,labels:[foo]
// A,A,1,w |
// A,B,2,x |
// | |
// QualifierRegex("B") |
// | |
// A,B,2,x |
// | |
// +--------------------------------+
// |
// A,A,1,w,labels:[foo]
// A,B,2,x,labels:[foo] // could be switched
// A,B,2,x // could be switched
//
// Despite being excluded by the qualifier filter, a copy of every cell
// that reaches the sink is present in the final result.
//
// As with an [Interleave][google.bigtable.v1.RowFilter.Interleave],
// duplicate cells are possible, and appear in an unspecified mutual order.
// In this case we have a duplicate with column "A:B" and timestamp 2,
// because one copy passed through the all filter while the other was
// passed through the label and sink. Note that one copy has label "foo",
// while the other does not.
//
// Cannot be used within the `predicate_filter`, `true_filter`, or
// `false_filter` of a [Condition][google.bigtable.v1.RowFilter.Condition].
bool sink = 16;
// Matches all cells, regardless of input. Functionally equivalent to
// leaving `filter` unset, but included for completeness.
bool pass_all_filter = 17;
// Does not match any cells, regardless of input. Useful for temporarily
// disabling just part of a filter.
bool block_all_filter = 18;
// Matches only cells from rows whose keys satisfy the given RE2 regex. In
// other words, passes through the entire row when the key matches, and
// otherwise produces an empty row.
// Note that, since row keys can contain arbitrary bytes, the '\C' escape
// sequence must be used if a true wildcard is desired. The '.' character
// will not match the new line character '\n', which may be present in a
// binary key.
bytes row_key_regex_filter = 4;
// Matches all cells from a row with probability p, and matches no cells
// from the row with probability 1-p.
double row_sample_filter = 14;
// Matches only cells from columns whose families satisfy the given RE2
// regex. For technical reasons, the regex must not contain the ':'
// character, even if it is not being used as a literal.
// Note that, since column families cannot contain the new line character
// '\n', it is sufficient to use '.' as a full wildcard when matching
// column family names.
string family_name_regex_filter = 5;
// Matches only cells from columns whose qualifiers satisfy the given RE2
// regex.
// Note that, since column qualifiers can contain arbitrary bytes, the '\C'
// escape sequence must be used if a true wildcard is desired. The '.'
// character will not match the new line character '\n', which may be
// present in a binary qualifier.
bytes column_qualifier_regex_filter = 6;
// Matches only cells from columns within the given range.
ColumnRange column_range_filter = 7;
// Matches only cells with timestamps within the given range.
TimestampRange timestamp_range_filter = 8;
// Matches only cells with values that satisfy the given regular expression.
// Note that, since cell values can contain arbitrary bytes, the '\C' escape
// sequence must be used if a true wildcard is desired. The '.' character
// will not match the new line character '\n', which may be present in a
// binary value.
bytes value_regex_filter = 9;
// Matches only cells with values that fall within the given range.
ValueRange value_range_filter = 15;
// Skips the first N cells of each row, matching all subsequent cells.
int32 cells_per_row_offset_filter = 10;
// Matches only the first N cells of each row.
int32 cells_per_row_limit_filter = 11;
// Matches only the most recent N cells within each column. For example,
// if N=2, this filter would match column "foo:bar" at timestamps 10 and 9,
// skip all earlier cells in "foo:bar", and then begin matching again in
// column "foo:bar2".
int32 cells_per_column_limit_filter = 12;
// Replaces each cell's value with the empty string.
bool strip_value_transformer = 13;
// Applies the given label to all cells in the output row. This allows
// the client to determine which results were produced from which part of
// the filter.
//
// Values must be at most 15 characters in length, and match the RE2
// pattern [a-z0-9\\-]+
//
// Due to a technical limitation, it is not currently possible to apply
// multiple labels to a cell. As a result, a Chain may have no more than
// one sub-filter which contains a apply_label_transformer. It is okay for
// an Interleave to contain multiple apply_label_transformers, as they will
// be applied to separate copies of the input. This may be relaxed in the
// future.
string apply_label_transformer = 19;
}
}
// Specifies a particular change to be made to the contents of a row.
message Mutation {
// A Mutation which sets the value of the specified cell.
message SetCell {
// The name of the family into which new data should be written.
// Must match [-_.a-zA-Z0-9]+
string family_name = 1;
// The qualifier of the column into which new data should be written.
// Can be any byte string, including the empty string.
bytes column_qualifier = 2;
// The timestamp of the cell into which new data should be written.
// Use -1 for current Bigtable server time.
// Otherwise, the client should set this value itself, noting that the
// default value is a timestamp of zero if the field is left unspecified.
// Values must match the "granularity" of the table (e.g. micros, millis).
int64 timestamp_micros = 3;
// The value to be written into the specified cell.
bytes value = 4;
}
// A Mutation which deletes cells from the specified column, optionally
// restricting the deletions to a given timestamp range.
message DeleteFromColumn {
// The name of the family from which cells should be deleted.
// Must match [-_.a-zA-Z0-9]+
string family_name = 1;
// The qualifier of the column from which cells should be deleted.
// Can be any byte string, including the empty string.
bytes column_qualifier = 2;
// The range of timestamps within which cells should be deleted.
TimestampRange time_range = 3;
}
// A Mutation which deletes all cells from the specified column family.
message DeleteFromFamily {
// The name of the family from which cells should be deleted.
// Must match [-_.a-zA-Z0-9]+
string family_name = 1;
}
// A Mutation which deletes all cells from the containing row.
message DeleteFromRow {
}
// Which of the possible Mutation types to apply.
oneof mutation {
// Set a cell's value.
SetCell set_cell = 1;
// Deletes cells from a column.
DeleteFromColumn delete_from_column = 2;
// Deletes cells from a column family.
DeleteFromFamily delete_from_family = 3;
// Deletes cells from the entire row.
DeleteFromRow delete_from_row = 4;
}
}
// Specifies an atomic read/modify/write operation on the latest value of the
// specified column.
message ReadModifyWriteRule {
// The name of the family to which the read/modify/write should be applied.
// Must match [-_.a-zA-Z0-9]+
string family_name = 1;
// The qualifier of the column to which the read/modify/write should be
// applied.
// Can be any byte string, including the empty string.
bytes column_qualifier = 2;
// The rule used to determine the column's new latest value from its current
// latest value.
oneof rule {
// Rule specifying that "append_value" be appended to the existing value.
// If the targeted cell is unset, it will be treated as containing the
// empty string.
bytes append_value = 3;
// Rule specifying that "increment_amount" be added to the existing value.
// If the targeted cell is unset, it will be treated as containing a zero.
// Otherwise, the targeted cell must contain an 8-byte value (interpreted
// as a 64-bit big-endian signed integer), or the entire request will fail.
int64 increment_amount = 4;
}
}

View file

@ -0,0 +1,81 @@
// Code generated by protoc-gen-go.
// source: google.golang.org/cloud/bigtable/internal/duration_proto/duration.proto
// DO NOT EDIT!
/*
Package google_protobuf is a generated protocol buffer package.
It is generated from these files:
google.golang.org/cloud/bigtable/internal/duration_proto/duration.proto
It has these top-level messages:
Duration
*/
package google_protobuf
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// A Duration represents a signed, fixed-length span of time represented
// as a count of seconds and fractions of seconds at nanosecond
// resolution. It is independent of any calendar and concepts like "day"
// or "month". It is related to Timestamp in that the difference between
// two Timestamp values is a Duration and it can be added or subtracted
// from a Timestamp. Range is approximately +-10,000 years.
//
// Example 1: Compute Duration from two Timestamps in pseudo code.
//
// Timestamp start = ...;
// Timestamp end = ...;
// Duration duration = ...;
//
// duration.seconds = end.seconds - start.seconds;
// duration.nanos = end.nanos - start.nanos;
//
// if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1;
// duration.nanos -= 1000000000;
// } else if (durations.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1;
// duration.nanos += 1000000000;
// }
//
// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
//
// Timestamp start = ...;
// Duration duration = ...;
// Timestamp end = ...;
//
// end.seconds = start.seconds + duration.seconds;
// end.nanos = start.nanos + duration.nanos;
//
// if (end.nanos < 0) {
// end.seconds -= 1;
// end.nanos += 1000000000;
// } else if (end.nanos >= 1000000000) {
// end.seconds += 1;
// end.nanos -= 1000000000;
// }
//
type Duration struct {
// Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive.
Seconds int64 `protobuf:"varint,1,opt,name=seconds" json:"seconds,omitempty"`
// Signed fractions of a second at nanosecond resolution of the span
// of time. Durations less than one second are represented with a 0
// `seconds` field and a positive or negative `nanos` field. For durations
// of one second or more, a non-zero value for the `nanos` field must be
// of the same sign as the `seconds` field. Must be from -999,999,999
// to +999,999,999 inclusive.
Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"`
}
func (m *Duration) Reset() { *m = Duration{} }
func (m *Duration) String() string { return proto.CompactTextString(m) }
func (*Duration) ProtoMessage() {}

View file

@ -0,0 +1,78 @@
// Copyright (c) 2015, Google Inc.
//
// 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.
syntax = "proto3";
package google.protobuf;
option java_generate_equals_and_hash = true;
option java_multiple_files = true;
option java_outer_classname = "DurationProto";
option java_package = "com.google.protobuf";
// A Duration represents a signed, fixed-length span of time represented
// as a count of seconds and fractions of seconds at nanosecond
// resolution. It is independent of any calendar and concepts like "day"
// or "month". It is related to Timestamp in that the difference between
// two Timestamp values is a Duration and it can be added or subtracted
// from a Timestamp. Range is approximately +-10,000 years.
//
// Example 1: Compute Duration from two Timestamps in pseudo code.
//
// Timestamp start = ...;
// Timestamp end = ...;
// Duration duration = ...;
//
// duration.seconds = end.seconds - start.seconds;
// duration.nanos = end.nanos - start.nanos;
//
// if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1;
// duration.nanos -= 1000000000;
// } else if (durations.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1;
// duration.nanos += 1000000000;
// }
//
// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
//
// Timestamp start = ...;
// Duration duration = ...;
// Timestamp end = ...;
//
// end.seconds = start.seconds + duration.seconds;
// end.nanos = start.nanos + duration.nanos;
//
// if (end.nanos < 0) {
// end.seconds -= 1;
// end.nanos += 1000000000;
// } else if (end.nanos >= 1000000000) {
// end.seconds += 1;
// end.nanos -= 1000000000;
// }
//
message Duration {
// Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive.
int64 seconds = 1;
// Signed fractions of a second at nanosecond resolution of the span
// of time. Durations less than one second are represented with a 0
// `seconds` field and a positive or negative `nanos` field. For durations
// of one second or more, a non-zero value for the `nanos` field must be
// of the same sign as the `seconds` field. Must be from -999,999,999
// to +999,999,999 inclusive.
int32 nanos = 2;
}

View file

@ -0,0 +1,38 @@
// Code generated by protoc-gen-go.
// source: google.golang.org/cloud/bigtable/internal/empty/empty.proto
// DO NOT EDIT!
/*
Package google_protobuf is a generated protocol buffer package.
It is generated from these files:
google.golang.org/cloud/bigtable/internal/empty/empty.proto
It has these top-level messages:
Empty
*/
package google_protobuf
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// A generic empty message that you can re-use to avoid defining duplicated
// empty messages in your APIs. A typical example is to use it as the request
// or the response type of an API method. For instance:
//
// service Foo {
// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
// }
//
type Empty struct {
}
func (m *Empty) Reset() { *m = Empty{} }
func (m *Empty) String() string { return proto.CompactTextString(m) }
func (*Empty) ProtoMessage() {}

View file

@ -0,0 +1,34 @@
// Copyright (c) 2015, Google Inc.
//
// 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.
syntax = "proto3";
package google.protobuf;
option java_multiple_files = true;
option java_outer_classname = "EmptyProto";
option java_package = "com.google.protobuf";
// A generic empty message that you can re-use to avoid defining duplicated
// empty messages in your APIs. A typical example is to use it as the request
// or the response type of an API method. For instance:
//
// service Foo {
// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
// }
//
message Empty {
}

76
vendor/google.golang.org/cloud/bigtable/internal/regen.sh generated vendored Executable file
View file

@ -0,0 +1,76 @@
#!/bin/bash -e
#
# This script rebuilds the generated code for the protocol buffers.
# To run this you will need protoc and goprotobuf installed;
# see https://github.com/golang/protobuf for instructions.
# You also need Go and Git installed.
PKG=google.golang.org/cloud/bigtable
UPSTREAM=https://github.com/GoogleCloudPlatform/cloud-bigtable-client
UPSTREAM_SUBDIR=bigtable-protos/src/main/proto
function die() {
echo 1>&2 $*
exit 1
}
# Sanity check that the right tools are accessible.
for tool in go git protoc protoc-gen-go; do
q=$(which $tool) || die "didn't find $tool"
echo 1>&2 "$tool: $q"
done
tmpdir=$(mktemp -d -t regen-cbt.XXXXXX)
trap 'rm -rf $tmpdir' EXIT
echo -n 1>&2 "finding package dir... "
pkgdir=$(go list -f '{{.Dir}}' $PKG)
echo 1>&2 $pkgdir
base=$(echo $pkgdir | sed "s,/$PKG\$,,")
echo 1>&2 "base: $base"
cd $base
echo 1>&2 "fetching latest protos... "
git clone -q $UPSTREAM $tmpdir
# Pass 1: build mapping from upstream filename to our filename.
declare -A filename_map
for f in $(cd $PKG && find internal -name '*.proto'); do
echo -n 1>&2 "looking for latest version of $f... "
up=$(cd $tmpdir/$UPSTREAM_SUBDIR && find * -name $(basename $f))
echo 1>&2 $up
if [ $(echo $up | wc -w) != "1" ]; then
die "not exactly one match"
fi
filename_map[$up]=$f
done
# Pass 2: build sed script for fixing imports.
import_fixes=$tmpdir/fix_imports.sed
for up in "${!filename_map[@]}"; do
f=${filename_map[$up]}
echo >>$import_fixes "s,\"$up\",\"$PKG/$f\","
done
cat $import_fixes | sed 's,^,### ,' 1>&2
# Pass 3: copy files, making necessary adjustments.
for up in "${!filename_map[@]}"; do
f=${filename_map[$up]}
cat $tmpdir/$UPSTREAM_SUBDIR/$up |
# Adjust proto imports.
sed -f $import_fixes |
# Drop the UndeleteCluster RPC method. It returns a google.longrunning.Operation.
sed '/^ rpc UndeleteCluster(/,/^ }$/d' |
# Drop annotations, long-running operations and timestamps. They aren't supported (yet).
sed '/"google\/longrunning\/operations.proto"/d' |
sed '/google.longrunning.Operation/d' |
sed '/"google\/protobuf\/timestamp.proto"/d' |
sed '/google\.protobuf\.Timestamp/d' |
sed '/"google\/api\/annotations.proto"/d' |
sed '/option.*google\.api\.http.*{.*};$/d' |
cat > $PKG/$f
done
# Run protoc once per package.
for dir in $(find $PKG/internal -name '*.proto' | xargs dirname | sort | uniq); do
echo 1>&2 "* $dir"
protoc --go_out=plugins=grpc:. $dir/*.proto
done
echo 1>&2 "All OK"

View file

@ -0,0 +1,287 @@
// Code generated by protoc-gen-go.
// source: google.golang.org/cloud/bigtable/internal/service_proto/bigtable_service.proto
// DO NOT EDIT!
package google_bigtable_v1
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_bigtable_v11 "google.golang.org/cloud/bigtable/internal/data_proto"
import google_protobuf "google.golang.org/cloud/bigtable/internal/empty"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// Client API for BigtableService service
type BigtableServiceClient interface {
// Streams back the contents of all requested rows, optionally applying
// the same Reader filter to each. Depending on their size, rows may be
// broken up across multiple responses, but atomicity of each row will still
// be preserved.
ReadRows(ctx context.Context, in *ReadRowsRequest, opts ...grpc.CallOption) (BigtableService_ReadRowsClient, error)
// Returns a sample of row keys in the table. The returned row keys will
// delimit contiguous sections of the table of approximately equal size,
// which can be used to break up the data for distributed tasks like
// mapreduces.
SampleRowKeys(ctx context.Context, in *SampleRowKeysRequest, opts ...grpc.CallOption) (BigtableService_SampleRowKeysClient, error)
// Mutates a row atomically. Cells already present in the row are left
// unchanged unless explicitly changed by 'mutation'.
MutateRow(ctx context.Context, in *MutateRowRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error)
// Mutates a row atomically based on the output of a predicate Reader filter.
CheckAndMutateRow(ctx context.Context, in *CheckAndMutateRowRequest, opts ...grpc.CallOption) (*CheckAndMutateRowResponse, error)
// Modifies a row atomically, reading the latest existing timestamp/value from
// the specified columns and writing a new value at
// max(existing timestamp, current server time) based on pre-defined
// read/modify/write rules. Returns the new contents of all modified cells.
ReadModifyWriteRow(ctx context.Context, in *ReadModifyWriteRowRequest, opts ...grpc.CallOption) (*google_bigtable_v11.Row, error)
}
type bigtableServiceClient struct {
cc *grpc.ClientConn
}
func NewBigtableServiceClient(cc *grpc.ClientConn) BigtableServiceClient {
return &bigtableServiceClient{cc}
}
func (c *bigtableServiceClient) ReadRows(ctx context.Context, in *ReadRowsRequest, opts ...grpc.CallOption) (BigtableService_ReadRowsClient, error) {
stream, err := grpc.NewClientStream(ctx, &_BigtableService_serviceDesc.Streams[0], c.cc, "/google.bigtable.v1.BigtableService/ReadRows", opts...)
if err != nil {
return nil, err
}
x := &bigtableServiceReadRowsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type BigtableService_ReadRowsClient interface {
Recv() (*ReadRowsResponse, error)
grpc.ClientStream
}
type bigtableServiceReadRowsClient struct {
grpc.ClientStream
}
func (x *bigtableServiceReadRowsClient) Recv() (*ReadRowsResponse, error) {
m := new(ReadRowsResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *bigtableServiceClient) SampleRowKeys(ctx context.Context, in *SampleRowKeysRequest, opts ...grpc.CallOption) (BigtableService_SampleRowKeysClient, error) {
stream, err := grpc.NewClientStream(ctx, &_BigtableService_serviceDesc.Streams[1], c.cc, "/google.bigtable.v1.BigtableService/SampleRowKeys", opts...)
if err != nil {
return nil, err
}
x := &bigtableServiceSampleRowKeysClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type BigtableService_SampleRowKeysClient interface {
Recv() (*SampleRowKeysResponse, error)
grpc.ClientStream
}
type bigtableServiceSampleRowKeysClient struct {
grpc.ClientStream
}
func (x *bigtableServiceSampleRowKeysClient) Recv() (*SampleRowKeysResponse, error) {
m := new(SampleRowKeysResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *bigtableServiceClient) MutateRow(ctx context.Context, in *MutateRowRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) {
out := new(google_protobuf.Empty)
err := grpc.Invoke(ctx, "/google.bigtable.v1.BigtableService/MutateRow", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableServiceClient) CheckAndMutateRow(ctx context.Context, in *CheckAndMutateRowRequest, opts ...grpc.CallOption) (*CheckAndMutateRowResponse, error) {
out := new(CheckAndMutateRowResponse)
err := grpc.Invoke(ctx, "/google.bigtable.v1.BigtableService/CheckAndMutateRow", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableServiceClient) ReadModifyWriteRow(ctx context.Context, in *ReadModifyWriteRowRequest, opts ...grpc.CallOption) (*google_bigtable_v11.Row, error) {
out := new(google_bigtable_v11.Row)
err := grpc.Invoke(ctx, "/google.bigtable.v1.BigtableService/ReadModifyWriteRow", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for BigtableService service
type BigtableServiceServer interface {
// Streams back the contents of all requested rows, optionally applying
// the same Reader filter to each. Depending on their size, rows may be
// broken up across multiple responses, but atomicity of each row will still
// be preserved.
ReadRows(*ReadRowsRequest, BigtableService_ReadRowsServer) error
// Returns a sample of row keys in the table. The returned row keys will
// delimit contiguous sections of the table of approximately equal size,
// which can be used to break up the data for distributed tasks like
// mapreduces.
SampleRowKeys(*SampleRowKeysRequest, BigtableService_SampleRowKeysServer) error
// Mutates a row atomically. Cells already present in the row are left
// unchanged unless explicitly changed by 'mutation'.
MutateRow(context.Context, *MutateRowRequest) (*google_protobuf.Empty, error)
// Mutates a row atomically based on the output of a predicate Reader filter.
CheckAndMutateRow(context.Context, *CheckAndMutateRowRequest) (*CheckAndMutateRowResponse, error)
// Modifies a row atomically, reading the latest existing timestamp/value from
// the specified columns and writing a new value at
// max(existing timestamp, current server time) based on pre-defined
// read/modify/write rules. Returns the new contents of all modified cells.
ReadModifyWriteRow(context.Context, *ReadModifyWriteRowRequest) (*google_bigtable_v11.Row, error)
}
func RegisterBigtableServiceServer(s *grpc.Server, srv BigtableServiceServer) {
s.RegisterService(&_BigtableService_serviceDesc, srv)
}
func _BigtableService_ReadRows_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(ReadRowsRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(BigtableServiceServer).ReadRows(m, &bigtableServiceReadRowsServer{stream})
}
type BigtableService_ReadRowsServer interface {
Send(*ReadRowsResponse) error
grpc.ServerStream
}
type bigtableServiceReadRowsServer struct {
grpc.ServerStream
}
func (x *bigtableServiceReadRowsServer) Send(m *ReadRowsResponse) error {
return x.ServerStream.SendMsg(m)
}
func _BigtableService_SampleRowKeys_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SampleRowKeysRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(BigtableServiceServer).SampleRowKeys(m, &bigtableServiceSampleRowKeysServer{stream})
}
type BigtableService_SampleRowKeysServer interface {
Send(*SampleRowKeysResponse) error
grpc.ServerStream
}
type bigtableServiceSampleRowKeysServer struct {
grpc.ServerStream
}
func (x *bigtableServiceSampleRowKeysServer) Send(m *SampleRowKeysResponse) error {
return x.ServerStream.SendMsg(m)
}
func _BigtableService_MutateRow_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(MutateRowRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableServiceServer).MutateRow(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableService_CheckAndMutateRow_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(CheckAndMutateRowRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableServiceServer).CheckAndMutateRow(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableService_ReadModifyWriteRow_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(ReadModifyWriteRowRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableServiceServer).ReadModifyWriteRow(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
var _BigtableService_serviceDesc = grpc.ServiceDesc{
ServiceName: "google.bigtable.v1.BigtableService",
HandlerType: (*BigtableServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "MutateRow",
Handler: _BigtableService_MutateRow_Handler,
},
{
MethodName: "CheckAndMutateRow",
Handler: _BigtableService_CheckAndMutateRow_Handler,
},
{
MethodName: "ReadModifyWriteRow",
Handler: _BigtableService_ReadModifyWriteRow_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "ReadRows",
Handler: _BigtableService_ReadRows_Handler,
ServerStreams: true,
},
{
StreamName: "SampleRowKeys",
Handler: _BigtableService_SampleRowKeys_Handler,
ServerStreams: true,
},
},
}

View file

@ -0,0 +1,60 @@
// Copyright (c) 2015, Google Inc.
//
// 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.
syntax = "proto3";
package google.bigtable.v1;
import "google.golang.org/cloud/bigtable/internal/data_proto/bigtable_data.proto";
import "google.golang.org/cloud/bigtable/internal/service_proto/bigtable_service_messages.proto";
import "google.golang.org/cloud/bigtable/internal/empty/empty.proto";
option java_generic_services = true;
option java_multiple_files = true;
option java_outer_classname = "BigtableServicesProto";
option java_package = "com.google.bigtable.v1";
// Service for reading from and writing to existing Bigtables.
service BigtableService {
// Streams back the contents of all requested rows, optionally applying
// the same Reader filter to each. Depending on their size, rows may be
// broken up across multiple responses, but atomicity of each row will still
// be preserved.
rpc ReadRows(ReadRowsRequest) returns (stream ReadRowsResponse) {
}
// Returns a sample of row keys in the table. The returned row keys will
// delimit contiguous sections of the table of approximately equal size,
// which can be used to break up the data for distributed tasks like
// mapreduces.
rpc SampleRowKeys(SampleRowKeysRequest) returns (stream SampleRowKeysResponse) {
}
// Mutates a row atomically. Cells already present in the row are left
// unchanged unless explicitly changed by 'mutation'.
rpc MutateRow(MutateRowRequest) returns (google.protobuf.Empty) {
}
// Mutates a row atomically based on the output of a predicate Reader filter.
rpc CheckAndMutateRow(CheckAndMutateRowRequest) returns (CheckAndMutateRowResponse) {
}
// Modifies a row atomically, reading the latest existing timestamp/value from
// the specified columns and writing a new value at
// max(existing timestamp, current server time) based on pre-defined
// read/modify/write rules. Returns the new contents of all modified cells.
rpc ReadModifyWriteRow(ReadModifyWriteRowRequest) returns (Row) {
}
}

View file

@ -0,0 +1,444 @@
// Code generated by protoc-gen-go.
// source: google.golang.org/cloud/bigtable/internal/service_proto/bigtable_service_messages.proto
// DO NOT EDIT!
/*
Package google_bigtable_v1 is a generated protocol buffer package.
It is generated from these files:
google.golang.org/cloud/bigtable/internal/service_proto/bigtable_service_messages.proto
google.golang.org/cloud/bigtable/internal/service_proto/bigtable_service.proto
It has these top-level messages:
ReadRowsRequest
ReadRowsResponse
SampleRowKeysRequest
SampleRowKeysResponse
MutateRowRequest
CheckAndMutateRowRequest
CheckAndMutateRowResponse
ReadModifyWriteRowRequest
*/
package google_bigtable_v1
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_bigtable_v11 "google.golang.org/cloud/bigtable/internal/data_proto"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// Request message for BigtableServer.ReadRows.
type ReadRowsRequest struct {
// The unique name of the table from which to read.
TableName string `protobuf:"bytes,1,opt,name=table_name" json:"table_name,omitempty"`
// If neither row_key nor row_range is set, reads from all rows.
//
// Types that are valid to be assigned to Target:
// *ReadRowsRequest_RowKey
// *ReadRowsRequest_RowRange
Target isReadRowsRequest_Target `protobuf_oneof:"target"`
// The filter to apply to the contents of the specified row(s). If unset,
// reads the entire table.
Filter *google_bigtable_v11.RowFilter `protobuf:"bytes,5,opt,name=filter" json:"filter,omitempty"`
// By default, rows are read sequentially, producing results which are
// guaranteed to arrive in increasing row order. Setting
// "allow_row_interleaving" to true allows multiple rows to be interleaved in
// the response stream, which increases throughput but breaks this guarantee,
// and may force the client to use more memory to buffer partially-received
// rows. Cannot be set to true when specifying "num_rows_limit".
AllowRowInterleaving bool `protobuf:"varint,6,opt,name=allow_row_interleaving" json:"allow_row_interleaving,omitempty"`
// The read will terminate after committing to N rows' worth of results. The
// default (zero) is to return all results.
// Note that "allow_row_interleaving" cannot be set to true when this is set.
NumRowsLimit int64 `protobuf:"varint,7,opt,name=num_rows_limit" json:"num_rows_limit,omitempty"`
}
func (m *ReadRowsRequest) Reset() { *m = ReadRowsRequest{} }
func (m *ReadRowsRequest) String() string { return proto.CompactTextString(m) }
func (*ReadRowsRequest) ProtoMessage() {}
type isReadRowsRequest_Target interface {
isReadRowsRequest_Target()
}
type ReadRowsRequest_RowKey struct {
RowKey []byte `protobuf:"bytes,2,opt,name=row_key,proto3,oneof"`
}
type ReadRowsRequest_RowRange struct {
RowRange *google_bigtable_v11.RowRange `protobuf:"bytes,3,opt,name=row_range,oneof"`
}
func (*ReadRowsRequest_RowKey) isReadRowsRequest_Target() {}
func (*ReadRowsRequest_RowRange) isReadRowsRequest_Target() {}
func (m *ReadRowsRequest) GetTarget() isReadRowsRequest_Target {
if m != nil {
return m.Target
}
return nil
}
func (m *ReadRowsRequest) GetRowKey() []byte {
if x, ok := m.GetTarget().(*ReadRowsRequest_RowKey); ok {
return x.RowKey
}
return nil
}
func (m *ReadRowsRequest) GetRowRange() *google_bigtable_v11.RowRange {
if x, ok := m.GetTarget().(*ReadRowsRequest_RowRange); ok {
return x.RowRange
}
return nil
}
func (m *ReadRowsRequest) GetFilter() *google_bigtable_v11.RowFilter {
if m != nil {
return m.Filter
}
return nil
}
// XXX_OneofFuncs is for the internal use of the proto package.
func (*ReadRowsRequest) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), []interface{}) {
return _ReadRowsRequest_OneofMarshaler, _ReadRowsRequest_OneofUnmarshaler, []interface{}{
(*ReadRowsRequest_RowKey)(nil),
(*ReadRowsRequest_RowRange)(nil),
}
}
func _ReadRowsRequest_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
m := msg.(*ReadRowsRequest)
// target
switch x := m.Target.(type) {
case *ReadRowsRequest_RowKey:
b.EncodeVarint(2<<3 | proto.WireBytes)
b.EncodeRawBytes(x.RowKey)
case *ReadRowsRequest_RowRange:
b.EncodeVarint(3<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.RowRange); err != nil {
return err
}
case nil:
default:
return fmt.Errorf("ReadRowsRequest.Target has unexpected type %T", x)
}
return nil
}
func _ReadRowsRequest_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
m := msg.(*ReadRowsRequest)
switch tag {
case 2: // target.row_key
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeRawBytes(true)
m.Target = &ReadRowsRequest_RowKey{x}
return true, err
case 3: // target.row_range
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(google_bigtable_v11.RowRange)
err := b.DecodeMessage(msg)
m.Target = &ReadRowsRequest_RowRange{msg}
return true, err
default:
return false, nil
}
}
// Response message for BigtableService.ReadRows.
type ReadRowsResponse struct {
// The key of the row for which we're receiving data.
// Results will be received in increasing row key order, unless
// "allow_row_interleaving" was specified in the request.
RowKey []byte `protobuf:"bytes,1,opt,name=row_key,proto3" json:"row_key,omitempty"`
// One or more chunks of the row specified by "row_key".
Chunks []*ReadRowsResponse_Chunk `protobuf:"bytes,2,rep,name=chunks" json:"chunks,omitempty"`
}
func (m *ReadRowsResponse) Reset() { *m = ReadRowsResponse{} }
func (m *ReadRowsResponse) String() string { return proto.CompactTextString(m) }
func (*ReadRowsResponse) ProtoMessage() {}
func (m *ReadRowsResponse) GetChunks() []*ReadRowsResponse_Chunk {
if m != nil {
return m.Chunks
}
return nil
}
// Specifies a piece of a row's contents returned as part of the read
// response stream.
type ReadRowsResponse_Chunk struct {
// Types that are valid to be assigned to Chunk:
// *ReadRowsResponse_Chunk_RowContents
// *ReadRowsResponse_Chunk_ResetRow
// *ReadRowsResponse_Chunk_CommitRow
Chunk isReadRowsResponse_Chunk_Chunk `protobuf_oneof:"chunk"`
}
func (m *ReadRowsResponse_Chunk) Reset() { *m = ReadRowsResponse_Chunk{} }
func (m *ReadRowsResponse_Chunk) String() string { return proto.CompactTextString(m) }
func (*ReadRowsResponse_Chunk) ProtoMessage() {}
type isReadRowsResponse_Chunk_Chunk interface {
isReadRowsResponse_Chunk_Chunk()
}
type ReadRowsResponse_Chunk_RowContents struct {
RowContents *google_bigtable_v11.Family `protobuf:"bytes,1,opt,name=row_contents,oneof"`
}
type ReadRowsResponse_Chunk_ResetRow struct {
ResetRow bool `protobuf:"varint,2,opt,name=reset_row,oneof"`
}
type ReadRowsResponse_Chunk_CommitRow struct {
CommitRow bool `protobuf:"varint,3,opt,name=commit_row,oneof"`
}
func (*ReadRowsResponse_Chunk_RowContents) isReadRowsResponse_Chunk_Chunk() {}
func (*ReadRowsResponse_Chunk_ResetRow) isReadRowsResponse_Chunk_Chunk() {}
func (*ReadRowsResponse_Chunk_CommitRow) isReadRowsResponse_Chunk_Chunk() {}
func (m *ReadRowsResponse_Chunk) GetChunk() isReadRowsResponse_Chunk_Chunk {
if m != nil {
return m.Chunk
}
return nil
}
func (m *ReadRowsResponse_Chunk) GetRowContents() *google_bigtable_v11.Family {
if x, ok := m.GetChunk().(*ReadRowsResponse_Chunk_RowContents); ok {
return x.RowContents
}
return nil
}
func (m *ReadRowsResponse_Chunk) GetResetRow() bool {
if x, ok := m.GetChunk().(*ReadRowsResponse_Chunk_ResetRow); ok {
return x.ResetRow
}
return false
}
func (m *ReadRowsResponse_Chunk) GetCommitRow() bool {
if x, ok := m.GetChunk().(*ReadRowsResponse_Chunk_CommitRow); ok {
return x.CommitRow
}
return false
}
// XXX_OneofFuncs is for the internal use of the proto package.
func (*ReadRowsResponse_Chunk) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), []interface{}) {
return _ReadRowsResponse_Chunk_OneofMarshaler, _ReadRowsResponse_Chunk_OneofUnmarshaler, []interface{}{
(*ReadRowsResponse_Chunk_RowContents)(nil),
(*ReadRowsResponse_Chunk_ResetRow)(nil),
(*ReadRowsResponse_Chunk_CommitRow)(nil),
}
}
func _ReadRowsResponse_Chunk_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
m := msg.(*ReadRowsResponse_Chunk)
// chunk
switch x := m.Chunk.(type) {
case *ReadRowsResponse_Chunk_RowContents:
b.EncodeVarint(1<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.RowContents); err != nil {
return err
}
case *ReadRowsResponse_Chunk_ResetRow:
t := uint64(0)
if x.ResetRow {
t = 1
}
b.EncodeVarint(2<<3 | proto.WireVarint)
b.EncodeVarint(t)
case *ReadRowsResponse_Chunk_CommitRow:
t := uint64(0)
if x.CommitRow {
t = 1
}
b.EncodeVarint(3<<3 | proto.WireVarint)
b.EncodeVarint(t)
case nil:
default:
return fmt.Errorf("ReadRowsResponse_Chunk.Chunk has unexpected type %T", x)
}
return nil
}
func _ReadRowsResponse_Chunk_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
m := msg.(*ReadRowsResponse_Chunk)
switch tag {
case 1: // chunk.row_contents
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(google_bigtable_v11.Family)
err := b.DecodeMessage(msg)
m.Chunk = &ReadRowsResponse_Chunk_RowContents{msg}
return true, err
case 2: // chunk.reset_row
if wire != proto.WireVarint {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeVarint()
m.Chunk = &ReadRowsResponse_Chunk_ResetRow{x != 0}
return true, err
case 3: // chunk.commit_row
if wire != proto.WireVarint {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeVarint()
m.Chunk = &ReadRowsResponse_Chunk_CommitRow{x != 0}
return true, err
default:
return false, nil
}
}
// Request message for BigtableService.SampleRowKeys.
type SampleRowKeysRequest struct {
// The unique name of the table from which to sample row keys.
TableName string `protobuf:"bytes,1,opt,name=table_name" json:"table_name,omitempty"`
}
func (m *SampleRowKeysRequest) Reset() { *m = SampleRowKeysRequest{} }
func (m *SampleRowKeysRequest) String() string { return proto.CompactTextString(m) }
func (*SampleRowKeysRequest) ProtoMessage() {}
// Response message for BigtableService.SampleRowKeys.
type SampleRowKeysResponse struct {
// Sorted streamed sequence of sample row keys in the table. The table might
// have contents before the first row key in the list and after the last one,
// but a key containing the empty string indicates "end of table" and will be
// the last response given, if present.
// Note that row keys in this list may not have ever been written to or read
// from, and users should therefore not make any assumptions about the row key
// structure that are specific to their use case.
RowKey []byte `protobuf:"bytes,1,opt,name=row_key,proto3" json:"row_key,omitempty"`
// Approximate total storage space used by all rows in the table which precede
// "row_key". Buffering the contents of all rows between two subsequent
// samples would require space roughly equal to the difference in their
// "offset_bytes" fields.
OffsetBytes int64 `protobuf:"varint,2,opt,name=offset_bytes" json:"offset_bytes,omitempty"`
}
func (m *SampleRowKeysResponse) Reset() { *m = SampleRowKeysResponse{} }
func (m *SampleRowKeysResponse) String() string { return proto.CompactTextString(m) }
func (*SampleRowKeysResponse) ProtoMessage() {}
// Request message for BigtableService.MutateRow.
type MutateRowRequest struct {
// The unique name of the table to which the mutation should be applied.
TableName string `protobuf:"bytes,1,opt,name=table_name" json:"table_name,omitempty"`
// The key of the row to which the mutation should be applied.
RowKey []byte `protobuf:"bytes,2,opt,name=row_key,proto3" json:"row_key,omitempty"`
// Changes to be atomically applied to the specified row. Entries are applied
// in order, meaning that earlier mutations can be masked by later ones.
// Must contain at least one entry and at most 100000.
Mutations []*google_bigtable_v11.Mutation `protobuf:"bytes,3,rep,name=mutations" json:"mutations,omitempty"`
}
func (m *MutateRowRequest) Reset() { *m = MutateRowRequest{} }
func (m *MutateRowRequest) String() string { return proto.CompactTextString(m) }
func (*MutateRowRequest) ProtoMessage() {}
func (m *MutateRowRequest) GetMutations() []*google_bigtable_v11.Mutation {
if m != nil {
return m.Mutations
}
return nil
}
// Request message for BigtableService.CheckAndMutateRowRequest
type CheckAndMutateRowRequest struct {
// The unique name of the table to which the conditional mutation should be
// applied.
TableName string `protobuf:"bytes,1,opt,name=table_name" json:"table_name,omitempty"`
// The key of the row to which the conditional mutation should be applied.
RowKey []byte `protobuf:"bytes,2,opt,name=row_key,proto3" json:"row_key,omitempty"`
// The filter to be applied to the contents of the specified row. Depending
// on whether or not any results are yielded, either "true_mutations" or
// "false_mutations" will be executed. If unset, checks that the row contains
// any values at all.
PredicateFilter *google_bigtable_v11.RowFilter `protobuf:"bytes,6,opt,name=predicate_filter" json:"predicate_filter,omitempty"`
// Changes to be atomically applied to the specified row if "predicate_filter"
// yields at least one cell when applied to "row_key". Entries are applied in
// order, meaning that earlier mutations can be masked by later ones.
// Must contain at least one entry if "false_mutations" is empty, and at most
// 100000.
TrueMutations []*google_bigtable_v11.Mutation `protobuf:"bytes,4,rep,name=true_mutations" json:"true_mutations,omitempty"`
// Changes to be atomically applied to the specified row if "predicate_filter"
// does not yield any cells when applied to "row_key". Entries are applied in
// order, meaning that earlier mutations can be masked by later ones.
// Must contain at least one entry if "true_mutations" is empty, and at most
// 100000.
FalseMutations []*google_bigtable_v11.Mutation `protobuf:"bytes,5,rep,name=false_mutations" json:"false_mutations,omitempty"`
}
func (m *CheckAndMutateRowRequest) Reset() { *m = CheckAndMutateRowRequest{} }
func (m *CheckAndMutateRowRequest) String() string { return proto.CompactTextString(m) }
func (*CheckAndMutateRowRequest) ProtoMessage() {}
func (m *CheckAndMutateRowRequest) GetPredicateFilter() *google_bigtable_v11.RowFilter {
if m != nil {
return m.PredicateFilter
}
return nil
}
func (m *CheckAndMutateRowRequest) GetTrueMutations() []*google_bigtable_v11.Mutation {
if m != nil {
return m.TrueMutations
}
return nil
}
func (m *CheckAndMutateRowRequest) GetFalseMutations() []*google_bigtable_v11.Mutation {
if m != nil {
return m.FalseMutations
}
return nil
}
// Response message for BigtableService.CheckAndMutateRowRequest.
type CheckAndMutateRowResponse struct {
// Whether or not the request's "predicate_filter" yielded any results for
// the specified row.
PredicateMatched bool `protobuf:"varint,1,opt,name=predicate_matched" json:"predicate_matched,omitempty"`
}
func (m *CheckAndMutateRowResponse) Reset() { *m = CheckAndMutateRowResponse{} }
func (m *CheckAndMutateRowResponse) String() string { return proto.CompactTextString(m) }
func (*CheckAndMutateRowResponse) ProtoMessage() {}
// Request message for BigtableService.ReadModifyWriteRowRequest.
type ReadModifyWriteRowRequest struct {
// The unique name of the table to which the read/modify/write rules should be
// applied.
TableName string `protobuf:"bytes,1,opt,name=table_name" json:"table_name,omitempty"`
// The key of the row to which the read/modify/write rules should be applied.
RowKey []byte `protobuf:"bytes,2,opt,name=row_key,proto3" json:"row_key,omitempty"`
// Rules specifying how the specified row's contents are to be transformed
// into writes. Entries are applied in order, meaning that earlier rules will
// affect the results of later ones.
Rules []*google_bigtable_v11.ReadModifyWriteRule `protobuf:"bytes,3,rep,name=rules" json:"rules,omitempty"`
}
func (m *ReadModifyWriteRowRequest) Reset() { *m = ReadModifyWriteRowRequest{} }
func (m *ReadModifyWriteRowRequest) String() string { return proto.CompactTextString(m) }
func (*ReadModifyWriteRowRequest) ProtoMessage() {}
func (m *ReadModifyWriteRowRequest) GetRules() []*google_bigtable_v11.ReadModifyWriteRule {
if m != nil {
return m.Rules
}
return nil
}

View file

@ -0,0 +1,177 @@
// Copyright (c) 2015, Google Inc.
//
// 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.
syntax = "proto3";
package google.bigtable.v1;
import "google.golang.org/cloud/bigtable/internal/data_proto/bigtable_data.proto";
option java_multiple_files = true;
option java_outer_classname = "BigtableServiceMessagesProto";
option java_package = "com.google.bigtable.v1";
// Request message for BigtableServer.ReadRows.
message ReadRowsRequest {
// The unique name of the table from which to read.
string table_name = 1;
// If neither row_key nor row_range is set, reads from all rows.
oneof target {
// The key of a single row from which to read.
bytes row_key = 2;
// A range of rows from which to read.
RowRange row_range = 3;
}
// The filter to apply to the contents of the specified row(s). If unset,
// reads the entire table.
RowFilter filter = 5;
// By default, rows are read sequentially, producing results which are
// guaranteed to arrive in increasing row order. Setting
// "allow_row_interleaving" to true allows multiple rows to be interleaved in
// the response stream, which increases throughput but breaks this guarantee,
// and may force the client to use more memory to buffer partially-received
// rows. Cannot be set to true when specifying "num_rows_limit".
bool allow_row_interleaving = 6;
// The read will terminate after committing to N rows' worth of results. The
// default (zero) is to return all results.
// Note that "allow_row_interleaving" cannot be set to true when this is set.
int64 num_rows_limit = 7;
}
// Response message for BigtableService.ReadRows.
message ReadRowsResponse {
// Specifies a piece of a row's contents returned as part of the read
// response stream.
message Chunk {
oneof chunk {
// A subset of the data from a particular row. As long as no "reset_row"
// is received in between, multiple "row_contents" from the same row are
// from the same atomic view of that row, and will be received in the
// expected family/column/timestamp order.
Family row_contents = 1;
// Indicates that the client should drop all previous chunks for
// "row_key", as it will be re-read from the beginning.
bool reset_row = 2;
// Indicates that the client can safely process all previous chunks for
// "row_key", as its data has been fully read.
bool commit_row = 3;
}
}
// The key of the row for which we're receiving data.
// Results will be received in increasing row key order, unless
// "allow_row_interleaving" was specified in the request.
bytes row_key = 1;
// One or more chunks of the row specified by "row_key".
repeated Chunk chunks = 2;
}
// Request message for BigtableService.SampleRowKeys.
message SampleRowKeysRequest {
// The unique name of the table from which to sample row keys.
string table_name = 1;
}
// Response message for BigtableService.SampleRowKeys.
message SampleRowKeysResponse {
// Sorted streamed sequence of sample row keys in the table. The table might
// have contents before the first row key in the list and after the last one,
// but a key containing the empty string indicates "end of table" and will be
// the last response given, if present.
// Note that row keys in this list may not have ever been written to or read
// from, and users should therefore not make any assumptions about the row key
// structure that are specific to their use case.
bytes row_key = 1;
// Approximate total storage space used by all rows in the table which precede
// "row_key". Buffering the contents of all rows between two subsequent
// samples would require space roughly equal to the difference in their
// "offset_bytes" fields.
int64 offset_bytes = 2;
}
// Request message for BigtableService.MutateRow.
message MutateRowRequest {
// The unique name of the table to which the mutation should be applied.
string table_name = 1;
// The key of the row to which the mutation should be applied.
bytes row_key = 2;
// Changes to be atomically applied to the specified row. Entries are applied
// in order, meaning that earlier mutations can be masked by later ones.
// Must contain at least one entry and at most 100000.
repeated Mutation mutations = 3;
}
// Request message for BigtableService.CheckAndMutateRowRequest
message CheckAndMutateRowRequest {
// The unique name of the table to which the conditional mutation should be
// applied.
string table_name = 1;
// The key of the row to which the conditional mutation should be applied.
bytes row_key = 2;
// The filter to be applied to the contents of the specified row. Depending
// on whether or not any results are yielded, either "true_mutations" or
// "false_mutations" will be executed. If unset, checks that the row contains
// any values at all.
RowFilter predicate_filter = 6;
// Changes to be atomically applied to the specified row if "predicate_filter"
// yields at least one cell when applied to "row_key". Entries are applied in
// order, meaning that earlier mutations can be masked by later ones.
// Must contain at least one entry if "false_mutations" is empty, and at most
// 100000.
repeated Mutation true_mutations = 4;
// Changes to be atomically applied to the specified row if "predicate_filter"
// does not yield any cells when applied to "row_key". Entries are applied in
// order, meaning that earlier mutations can be masked by later ones.
// Must contain at least one entry if "true_mutations" is empty, and at most
// 100000.
repeated Mutation false_mutations = 5;
}
// Response message for BigtableService.CheckAndMutateRowRequest.
message CheckAndMutateRowResponse {
// Whether or not the request's "predicate_filter" yielded any results for
// the specified row.
bool predicate_matched = 1;
}
// Request message for BigtableService.ReadModifyWriteRowRequest.
message ReadModifyWriteRowRequest {
// The unique name of the table to which the read/modify/write rules should be
// applied.
string table_name = 1;
// The key of the row to which the read/modify/write rules should be applied.
bytes row_key = 2;
// Rules specifying how the specified row's contents are to be transformed
// into writes. Entries are applied in order, meaning that earlier rules will
// affect the results of later ones.
repeated ReadModifyWriteRule rules = 3;
}

View file

@ -0,0 +1,308 @@
// Code generated by protoc-gen-go.
// source: google.golang.org/cloud/bigtable/internal/table_data_proto/bigtable_table_data.proto
// DO NOT EDIT!
/*
Package google_bigtable_admin_table_v1 is a generated protocol buffer package.
It is generated from these files:
google.golang.org/cloud/bigtable/internal/table_data_proto/bigtable_table_data.proto
It has these top-level messages:
Table
ColumnFamily
GcRule
*/
package google_bigtable_admin_table_v1
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf "google.golang.org/cloud/bigtable/internal/duration_proto"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
type Table_TimestampGranularity int32
const (
Table_MILLIS Table_TimestampGranularity = 0
)
var Table_TimestampGranularity_name = map[int32]string{
0: "MILLIS",
}
var Table_TimestampGranularity_value = map[string]int32{
"MILLIS": 0,
}
func (x Table_TimestampGranularity) String() string {
return proto.EnumName(Table_TimestampGranularity_name, int32(x))
}
// A collection of user data indexed by row, column, and timestamp.
// Each table is served using the resources of its parent cluster.
type Table struct {
// A unique identifier of the form
// <cluster_name>/tables/[_a-zA-Z0-9][-_.a-zA-Z0-9]*
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// The column families configured for this table, mapped by column family id.
ColumnFamilies map[string]*ColumnFamily `protobuf:"bytes,3,rep,name=column_families" json:"column_families,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
// The granularity (e.g. MILLIS, MICROS) at which timestamps are stored in
// this table. Timestamps not matching the granularity will be rejected.
// Cannot be changed once the table is created.
Granularity Table_TimestampGranularity `protobuf:"varint,4,opt,name=granularity,enum=google.bigtable.admin.table.v1.Table_TimestampGranularity" json:"granularity,omitempty"`
}
func (m *Table) Reset() { *m = Table{} }
func (m *Table) String() string { return proto.CompactTextString(m) }
func (*Table) ProtoMessage() {}
func (m *Table) GetColumnFamilies() map[string]*ColumnFamily {
if m != nil {
return m.ColumnFamilies
}
return nil
}
// A set of columns within a table which share a common configuration.
type ColumnFamily struct {
// A unique identifier of the form <table_name>/columnFamilies/[-_.a-zA-Z0-9]+
// The last segment is the same as the "name" field in
// google.bigtable.v1.Family.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// Garbage collection expression specified by the following grammar:
// GC = EXPR
// | "" ;
// EXPR = EXPR, "||", EXPR (* lowest precedence *)
// | EXPR, "&&", EXPR
// | "(", EXPR, ")" (* highest precedence *)
// | PROP ;
// PROP = "version() >", NUM32
// | "age() >", NUM64, [ UNIT ] ;
// NUM32 = non-zero-digit { digit } ; (* # NUM32 <= 2^32 - 1 *)
// NUM64 = non-zero-digit { digit } ; (* # NUM64 <= 2^63 - 1 *)
// UNIT = "d" | "h" | "m" (* d=days, h=hours, m=minutes, else micros *)
// GC expressions can be up to 500 characters in length
//
// The different types of PROP are defined as follows:
// version() - cell index, counting from most recent and starting at 1
// age() - age of the cell (current time minus cell timestamp)
//
// Example: "version() > 3 || (age() > 3d && version() > 1)"
// drop cells beyond the most recent three, and drop cells older than three
// days unless they're the most recent cell in the row/column
//
// Garbage collection executes opportunistically in the background, and so
// it's possible for reads to return a cell even if it matches the active GC
// expression for its family.
GcExpression string `protobuf:"bytes,2,opt,name=gc_expression" json:"gc_expression,omitempty"`
// Garbage collection rule specified as a protobuf.
// Supersedes `gc_expression`.
// Must serialize to at most 500 bytes.
//
// NOTE: Garbage collection executes opportunistically in the background, and
// so it's possible for reads to return a cell even if it matches the active
// GC expression for its family.
GcRule *GcRule `protobuf:"bytes,3,opt,name=gc_rule" json:"gc_rule,omitempty"`
}
func (m *ColumnFamily) Reset() { *m = ColumnFamily{} }
func (m *ColumnFamily) String() string { return proto.CompactTextString(m) }
func (*ColumnFamily) ProtoMessage() {}
func (m *ColumnFamily) GetGcRule() *GcRule {
if m != nil {
return m.GcRule
}
return nil
}
// Rule for determining which cells to delete during garbage collection.
type GcRule struct {
// Types that are valid to be assigned to Rule:
// *GcRule_MaxNumVersions
// *GcRule_MaxAge
// *GcRule_Intersection_
// *GcRule_Union_
Rule isGcRule_Rule `protobuf_oneof:"rule"`
}
func (m *GcRule) Reset() { *m = GcRule{} }
func (m *GcRule) String() string { return proto.CompactTextString(m) }
func (*GcRule) ProtoMessage() {}
type isGcRule_Rule interface {
isGcRule_Rule()
}
type GcRule_MaxNumVersions struct {
MaxNumVersions int32 `protobuf:"varint,1,opt,name=max_num_versions,oneof"`
}
type GcRule_MaxAge struct {
MaxAge *google_protobuf.Duration `protobuf:"bytes,2,opt,name=max_age,oneof"`
}
type GcRule_Intersection_ struct {
Intersection *GcRule_Intersection `protobuf:"bytes,3,opt,name=intersection,oneof"`
}
type GcRule_Union_ struct {
Union *GcRule_Union `protobuf:"bytes,4,opt,name=union,oneof"`
}
func (*GcRule_MaxNumVersions) isGcRule_Rule() {}
func (*GcRule_MaxAge) isGcRule_Rule() {}
func (*GcRule_Intersection_) isGcRule_Rule() {}
func (*GcRule_Union_) isGcRule_Rule() {}
func (m *GcRule) GetRule() isGcRule_Rule {
if m != nil {
return m.Rule
}
return nil
}
func (m *GcRule) GetMaxNumVersions() int32 {
if x, ok := m.GetRule().(*GcRule_MaxNumVersions); ok {
return x.MaxNumVersions
}
return 0
}
func (m *GcRule) GetMaxAge() *google_protobuf.Duration {
if x, ok := m.GetRule().(*GcRule_MaxAge); ok {
return x.MaxAge
}
return nil
}
func (m *GcRule) GetIntersection() *GcRule_Intersection {
if x, ok := m.GetRule().(*GcRule_Intersection_); ok {
return x.Intersection
}
return nil
}
func (m *GcRule) GetUnion() *GcRule_Union {
if x, ok := m.GetRule().(*GcRule_Union_); ok {
return x.Union
}
return nil
}
// XXX_OneofFuncs is for the internal use of the proto package.
func (*GcRule) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), []interface{}) {
return _GcRule_OneofMarshaler, _GcRule_OneofUnmarshaler, []interface{}{
(*GcRule_MaxNumVersions)(nil),
(*GcRule_MaxAge)(nil),
(*GcRule_Intersection_)(nil),
(*GcRule_Union_)(nil),
}
}
func _GcRule_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
m := msg.(*GcRule)
// rule
switch x := m.Rule.(type) {
case *GcRule_MaxNumVersions:
b.EncodeVarint(1<<3 | proto.WireVarint)
b.EncodeVarint(uint64(x.MaxNumVersions))
case *GcRule_MaxAge:
b.EncodeVarint(2<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.MaxAge); err != nil {
return err
}
case *GcRule_Intersection_:
b.EncodeVarint(3<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.Intersection); err != nil {
return err
}
case *GcRule_Union_:
b.EncodeVarint(4<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.Union); err != nil {
return err
}
case nil:
default:
return fmt.Errorf("GcRule.Rule has unexpected type %T", x)
}
return nil
}
func _GcRule_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
m := msg.(*GcRule)
switch tag {
case 1: // rule.max_num_versions
if wire != proto.WireVarint {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeVarint()
m.Rule = &GcRule_MaxNumVersions{int32(x)}
return true, err
case 2: // rule.max_age
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(google_protobuf.Duration)
err := b.DecodeMessage(msg)
m.Rule = &GcRule_MaxAge{msg}
return true, err
case 3: // rule.intersection
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(GcRule_Intersection)
err := b.DecodeMessage(msg)
m.Rule = &GcRule_Intersection_{msg}
return true, err
case 4: // rule.union
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(GcRule_Union)
err := b.DecodeMessage(msg)
m.Rule = &GcRule_Union_{msg}
return true, err
default:
return false, nil
}
}
// A GcRule which deletes cells matching all of the given rules.
type GcRule_Intersection struct {
// Only delete cells which would be deleted by every element of `rules`.
Rules []*GcRule `protobuf:"bytes,1,rep,name=rules" json:"rules,omitempty"`
}
func (m *GcRule_Intersection) Reset() { *m = GcRule_Intersection{} }
func (m *GcRule_Intersection) String() string { return proto.CompactTextString(m) }
func (*GcRule_Intersection) ProtoMessage() {}
func (m *GcRule_Intersection) GetRules() []*GcRule {
if m != nil {
return m.Rules
}
return nil
}
// A GcRule which deletes cells matching any of the given rules.
type GcRule_Union struct {
// Delete cells which would be deleted by any element of `rules`.
Rules []*GcRule `protobuf:"bytes,1,rep,name=rules" json:"rules,omitempty"`
}
func (m *GcRule_Union) Reset() { *m = GcRule_Union{} }
func (m *GcRule_Union) String() string { return proto.CompactTextString(m) }
func (*GcRule_Union) ProtoMessage() {}
func (m *GcRule_Union) GetRules() []*GcRule {
if m != nil {
return m.Rules
}
return nil
}
func init() {
proto.RegisterEnum("google.bigtable.admin.table.v1.Table_TimestampGranularity", Table_TimestampGranularity_name, Table_TimestampGranularity_value)
}

View file

@ -0,0 +1,123 @@
// Copyright (c) 2015, Google Inc.
//
// 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.
syntax = "proto3";
package google.bigtable.admin.table.v1;
import "google.golang.org/cloud/bigtable/internal/duration_proto/duration.proto";
option java_multiple_files = true;
option java_outer_classname = "BigtableTableDataProto";
option java_package = "com.google.bigtable.admin.table.v1";
// A collection of user data indexed by row, column, and timestamp.
// Each table is served using the resources of its parent cluster.
message Table {
enum TimestampGranularity {
MILLIS = 0;
}
// A unique identifier of the form
// <cluster_name>/tables/[_a-zA-Z0-9][-_.a-zA-Z0-9]*
string name = 1;
// If this Table is in the process of being created, the Operation used to
// track its progress. As long as this operation is present, the Table will
// not accept any Table Admin or Read/Write requests.
// The column families configured for this table, mapped by column family id.
map<string, ColumnFamily> column_families = 3;
// The granularity (e.g. MILLIS, MICROS) at which timestamps are stored in
// this table. Timestamps not matching the granularity will be rejected.
// Cannot be changed once the table is created.
TimestampGranularity granularity = 4;
}
// A set of columns within a table which share a common configuration.
message ColumnFamily {
// A unique identifier of the form <table_name>/columnFamilies/[-_.a-zA-Z0-9]+
// The last segment is the same as the "name" field in
// google.bigtable.v1.Family.
string name = 1;
// Garbage collection expression specified by the following grammar:
// GC = EXPR
// | "" ;
// EXPR = EXPR, "||", EXPR (* lowest precedence *)
// | EXPR, "&&", EXPR
// | "(", EXPR, ")" (* highest precedence *)
// | PROP ;
// PROP = "version() >", NUM32
// | "age() >", NUM64, [ UNIT ] ;
// NUM32 = non-zero-digit { digit } ; (* # NUM32 <= 2^32 - 1 *)
// NUM64 = non-zero-digit { digit } ; (* # NUM64 <= 2^63 - 1 *)
// UNIT = "d" | "h" | "m" (* d=days, h=hours, m=minutes, else micros *)
// GC expressions can be up to 500 characters in length
//
// The different types of PROP are defined as follows:
// version() - cell index, counting from most recent and starting at 1
// age() - age of the cell (current time minus cell timestamp)
//
// Example: "version() > 3 || (age() > 3d && version() > 1)"
// drop cells beyond the most recent three, and drop cells older than three
// days unless they're the most recent cell in the row/column
//
// Garbage collection executes opportunistically in the background, and so
// it's possible for reads to return a cell even if it matches the active GC
// expression for its family.
string gc_expression = 2;
// Garbage collection rule specified as a protobuf.
// Supersedes `gc_expression`.
// Must serialize to at most 500 bytes.
//
// NOTE: Garbage collection executes opportunistically in the background, and
// so it's possible for reads to return a cell even if it matches the active
// GC expression for its family.
GcRule gc_rule = 3;
}
// Rule for determining which cells to delete during garbage collection.
message GcRule {
// A GcRule which deletes cells matching all of the given rules.
message Intersection {
// Only delete cells which would be deleted by every element of `rules`.
repeated GcRule rules = 1;
}
// A GcRule which deletes cells matching any of the given rules.
message Union {
// Delete cells which would be deleted by any element of `rules`.
repeated GcRule rules = 1;
}
oneof rule {
// Delete all cells in a column except the most recent N.
int32 max_num_versions = 1;
// Delete cells in a column older than the given age.
// Values must be at least one millisecond, and will be truncated to
// microsecond granularity.
google.protobuf.Duration max_age = 2;
// Delete cells that would be deleted by every nested rule.
Intersection intersection = 3;
// Delete cells that would be deleted by any nested rule.
Union union = 4;
}
}

View file

@ -0,0 +1,293 @@
// Code generated by protoc-gen-go.
// source: google.golang.org/cloud/bigtable/internal/table_service_proto/bigtable_table_service.proto
// DO NOT EDIT!
package google_bigtable_admin_table_v1
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_bigtable_admin_table_v11 "google.golang.org/cloud/bigtable/internal/table_data_proto"
import google_protobuf1 "google.golang.org/cloud/bigtable/internal/empty"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// Client API for BigtableTableService service
type BigtableTableServiceClient interface {
// Creates a new table, to be served from a specified cluster.
// The table can be created with a full set of initial column families,
// specified in the request.
CreateTable(ctx context.Context, in *CreateTableRequest, opts ...grpc.CallOption) (*google_bigtable_admin_table_v11.Table, error)
// Lists the names of all tables served from a specified cluster.
ListTables(ctx context.Context, in *ListTablesRequest, opts ...grpc.CallOption) (*ListTablesResponse, error)
// Gets the schema of the specified table, including its column families.
GetTable(ctx context.Context, in *GetTableRequest, opts ...grpc.CallOption) (*google_bigtable_admin_table_v11.Table, error)
// Permanently deletes a specified table and all of its data.
DeleteTable(ctx context.Context, in *DeleteTableRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error)
// Changes the name of a specified table.
// Cannot be used to move tables between clusters, zones, or projects.
RenameTable(ctx context.Context, in *RenameTableRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error)
// Creates a new column family within a specified table.
CreateColumnFamily(ctx context.Context, in *CreateColumnFamilyRequest, opts ...grpc.CallOption) (*google_bigtable_admin_table_v11.ColumnFamily, error)
// Changes the configuration of a specified column family.
UpdateColumnFamily(ctx context.Context, in *google_bigtable_admin_table_v11.ColumnFamily, opts ...grpc.CallOption) (*google_bigtable_admin_table_v11.ColumnFamily, error)
// Permanently deletes a specified column family and all of its data.
DeleteColumnFamily(ctx context.Context, in *DeleteColumnFamilyRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error)
}
type bigtableTableServiceClient struct {
cc *grpc.ClientConn
}
func NewBigtableTableServiceClient(cc *grpc.ClientConn) BigtableTableServiceClient {
return &bigtableTableServiceClient{cc}
}
func (c *bigtableTableServiceClient) CreateTable(ctx context.Context, in *CreateTableRequest, opts ...grpc.CallOption) (*google_bigtable_admin_table_v11.Table, error) {
out := new(google_bigtable_admin_table_v11.Table)
err := grpc.Invoke(ctx, "/google.bigtable.admin.table.v1.BigtableTableService/CreateTable", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableTableServiceClient) ListTables(ctx context.Context, in *ListTablesRequest, opts ...grpc.CallOption) (*ListTablesResponse, error) {
out := new(ListTablesResponse)
err := grpc.Invoke(ctx, "/google.bigtable.admin.table.v1.BigtableTableService/ListTables", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableTableServiceClient) GetTable(ctx context.Context, in *GetTableRequest, opts ...grpc.CallOption) (*google_bigtable_admin_table_v11.Table, error) {
out := new(google_bigtable_admin_table_v11.Table)
err := grpc.Invoke(ctx, "/google.bigtable.admin.table.v1.BigtableTableService/GetTable", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableTableServiceClient) DeleteTable(ctx context.Context, in *DeleteTableRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) {
out := new(google_protobuf1.Empty)
err := grpc.Invoke(ctx, "/google.bigtable.admin.table.v1.BigtableTableService/DeleteTable", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableTableServiceClient) RenameTable(ctx context.Context, in *RenameTableRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) {
out := new(google_protobuf1.Empty)
err := grpc.Invoke(ctx, "/google.bigtable.admin.table.v1.BigtableTableService/RenameTable", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableTableServiceClient) CreateColumnFamily(ctx context.Context, in *CreateColumnFamilyRequest, opts ...grpc.CallOption) (*google_bigtable_admin_table_v11.ColumnFamily, error) {
out := new(google_bigtable_admin_table_v11.ColumnFamily)
err := grpc.Invoke(ctx, "/google.bigtable.admin.table.v1.BigtableTableService/CreateColumnFamily", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableTableServiceClient) UpdateColumnFamily(ctx context.Context, in *google_bigtable_admin_table_v11.ColumnFamily, opts ...grpc.CallOption) (*google_bigtable_admin_table_v11.ColumnFamily, error) {
out := new(google_bigtable_admin_table_v11.ColumnFamily)
err := grpc.Invoke(ctx, "/google.bigtable.admin.table.v1.BigtableTableService/UpdateColumnFamily", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bigtableTableServiceClient) DeleteColumnFamily(ctx context.Context, in *DeleteColumnFamilyRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) {
out := new(google_protobuf1.Empty)
err := grpc.Invoke(ctx, "/google.bigtable.admin.table.v1.BigtableTableService/DeleteColumnFamily", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for BigtableTableService service
type BigtableTableServiceServer interface {
// Creates a new table, to be served from a specified cluster.
// The table can be created with a full set of initial column families,
// specified in the request.
CreateTable(context.Context, *CreateTableRequest) (*google_bigtable_admin_table_v11.Table, error)
// Lists the names of all tables served from a specified cluster.
ListTables(context.Context, *ListTablesRequest) (*ListTablesResponse, error)
// Gets the schema of the specified table, including its column families.
GetTable(context.Context, *GetTableRequest) (*google_bigtable_admin_table_v11.Table, error)
// Permanently deletes a specified table and all of its data.
DeleteTable(context.Context, *DeleteTableRequest) (*google_protobuf1.Empty, error)
// Changes the name of a specified table.
// Cannot be used to move tables between clusters, zones, or projects.
RenameTable(context.Context, *RenameTableRequest) (*google_protobuf1.Empty, error)
// Creates a new column family within a specified table.
CreateColumnFamily(context.Context, *CreateColumnFamilyRequest) (*google_bigtable_admin_table_v11.ColumnFamily, error)
// Changes the configuration of a specified column family.
UpdateColumnFamily(context.Context, *google_bigtable_admin_table_v11.ColumnFamily) (*google_bigtable_admin_table_v11.ColumnFamily, error)
// Permanently deletes a specified column family and all of its data.
DeleteColumnFamily(context.Context, *DeleteColumnFamilyRequest) (*google_protobuf1.Empty, error)
}
func RegisterBigtableTableServiceServer(s *grpc.Server, srv BigtableTableServiceServer) {
s.RegisterService(&_BigtableTableService_serviceDesc, srv)
}
func _BigtableTableService_CreateTable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(CreateTableRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableTableServiceServer).CreateTable(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableTableService_ListTables_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(ListTablesRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableTableServiceServer).ListTables(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableTableService_GetTable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(GetTableRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableTableServiceServer).GetTable(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableTableService_DeleteTable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(DeleteTableRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableTableServiceServer).DeleteTable(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableTableService_RenameTable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(RenameTableRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableTableServiceServer).RenameTable(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableTableService_CreateColumnFamily_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(CreateColumnFamilyRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableTableServiceServer).CreateColumnFamily(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableTableService_UpdateColumnFamily_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(google_bigtable_admin_table_v11.ColumnFamily)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableTableServiceServer).UpdateColumnFamily(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _BigtableTableService_DeleteColumnFamily_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(DeleteColumnFamilyRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(BigtableTableServiceServer).DeleteColumnFamily(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
var _BigtableTableService_serviceDesc = grpc.ServiceDesc{
ServiceName: "google.bigtable.admin.table.v1.BigtableTableService",
HandlerType: (*BigtableTableServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CreateTable",
Handler: _BigtableTableService_CreateTable_Handler,
},
{
MethodName: "ListTables",
Handler: _BigtableTableService_ListTables_Handler,
},
{
MethodName: "GetTable",
Handler: _BigtableTableService_GetTable_Handler,
},
{
MethodName: "DeleteTable",
Handler: _BigtableTableService_DeleteTable_Handler,
},
{
MethodName: "RenameTable",
Handler: _BigtableTableService_RenameTable_Handler,
},
{
MethodName: "CreateColumnFamily",
Handler: _BigtableTableService_CreateColumnFamily_Handler,
},
{
MethodName: "UpdateColumnFamily",
Handler: _BigtableTableService_UpdateColumnFamily_Handler,
},
{
MethodName: "DeleteColumnFamily",
Handler: _BigtableTableService_DeleteColumnFamily_Handler,
},
},
Streams: []grpc.StreamDesc{},
}

View file

@ -0,0 +1,65 @@
// Copyright (c) 2015, Google Inc.
//
// 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.
syntax = "proto3";
package google.bigtable.admin.table.v1;
import "google.golang.org/cloud/bigtable/internal/table_data_proto/bigtable_table_data.proto";
import "google.golang.org/cloud/bigtable/internal/table_service_proto/bigtable_table_service_messages.proto";
import "google.golang.org/cloud/bigtable/internal/empty/empty.proto";
option java_multiple_files = true;
option java_outer_classname = "BigtableTableServicesProto";
option java_package = "com.google.bigtable.admin.table.v1";
// Service for creating, configuring, and deleting Cloud Bigtable tables.
// Provides access to the table schemas only, not the data stored within the tables.
service BigtableTableService {
// Creates a new table, to be served from a specified cluster.
// The table can be created with a full set of initial column families,
// specified in the request.
rpc CreateTable(CreateTableRequest) returns (Table) {
}
// Lists the names of all tables served from a specified cluster.
rpc ListTables(ListTablesRequest) returns (ListTablesResponse) {
}
// Gets the schema of the specified table, including its column families.
rpc GetTable(GetTableRequest) returns (Table) {
}
// Permanently deletes a specified table and all of its data.
rpc DeleteTable(DeleteTableRequest) returns (google.protobuf.Empty) {
}
// Changes the name of a specified table.
// Cannot be used to move tables between clusters, zones, or projects.
rpc RenameTable(RenameTableRequest) returns (google.protobuf.Empty) {
}
// Creates a new column family within a specified table.
rpc CreateColumnFamily(CreateColumnFamilyRequest) returns (ColumnFamily) {
}
// Changes the configuration of a specified column family.
rpc UpdateColumnFamily(ColumnFamily) returns (ColumnFamily) {
}
// Permanently deletes a specified column family and all of its data.
rpc DeleteColumnFamily(DeleteColumnFamilyRequest) returns (google.protobuf.Empty) {
}
}

View file

@ -0,0 +1,156 @@
// Code generated by protoc-gen-go.
// source: google.golang.org/cloud/bigtable/internal/table_service_proto/bigtable_table_service_messages.proto
// DO NOT EDIT!
/*
Package google_bigtable_admin_table_v1 is a generated protocol buffer package.
It is generated from these files:
google.golang.org/cloud/bigtable/internal/table_service_proto/bigtable_table_service_messages.proto
google.golang.org/cloud/bigtable/internal/table_service_proto/bigtable_table_service.proto
It has these top-level messages:
CreateTableRequest
ListTablesRequest
ListTablesResponse
GetTableRequest
DeleteTableRequest
RenameTableRequest
CreateColumnFamilyRequest
DeleteColumnFamilyRequest
*/
package google_bigtable_admin_table_v1
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_bigtable_admin_table_v11 "google.golang.org/cloud/bigtable/internal/table_data_proto"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
type CreateTableRequest struct {
// The unique name of the cluster in which to create the new table.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// The name by which the new table should be referred to within the cluster,
// e.g. "foobar" rather than "<cluster_name>/tables/foobar".
TableId string `protobuf:"bytes,2,opt,name=table_id" json:"table_id,omitempty"`
// The Table to create. The `name` field of the Table and all of its
// ColumnFamilies must be left blank, and will be populated in the response.
Table *google_bigtable_admin_table_v11.Table `protobuf:"bytes,3,opt,name=table" json:"table,omitempty"`
// The optional list of row keys that will be used to initially split the
// table into several tablets (Tablets are similar to HBase regions).
// Given two split keys, "s1" and "s2", three tablets will be created,
// spanning the key ranges: [, s1), [s1, s2), [s2, ).
//
// Example:
// * Row keys := ["a", "apple", "custom", "customer_1", "customer_2",
// "other", "zz"]
// * initial_split_keys := ["apple", "customer_1", "customer_2", "other"]
// * Key assignment:
// - Tablet 1 [, apple) => {"a"}.
// - Tablet 2 [apple, customer_1) => {"apple", "custom"}.
// - Tablet 3 [customer_1, customer_2) => {"customer_1"}.
// - Tablet 4 [customer_2, other) => {"customer_2"}.
// - Tablet 5 [other, ) => {"other", "zz"}.
InitialSplitKeys []string `protobuf:"bytes,4,rep,name=initial_split_keys" json:"initial_split_keys,omitempty"`
}
func (m *CreateTableRequest) Reset() { *m = CreateTableRequest{} }
func (m *CreateTableRequest) String() string { return proto.CompactTextString(m) }
func (*CreateTableRequest) ProtoMessage() {}
func (m *CreateTableRequest) GetTable() *google_bigtable_admin_table_v11.Table {
if m != nil {
return m.Table
}
return nil
}
type ListTablesRequest struct {
// The unique name of the cluster for which tables should be listed.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *ListTablesRequest) Reset() { *m = ListTablesRequest{} }
func (m *ListTablesRequest) String() string { return proto.CompactTextString(m) }
func (*ListTablesRequest) ProtoMessage() {}
type ListTablesResponse struct {
// The tables present in the requested cluster.
// At present, only the names of the tables are populated.
Tables []*google_bigtable_admin_table_v11.Table `protobuf:"bytes,1,rep,name=tables" json:"tables,omitempty"`
}
func (m *ListTablesResponse) Reset() { *m = ListTablesResponse{} }
func (m *ListTablesResponse) String() string { return proto.CompactTextString(m) }
func (*ListTablesResponse) ProtoMessage() {}
func (m *ListTablesResponse) GetTables() []*google_bigtable_admin_table_v11.Table {
if m != nil {
return m.Tables
}
return nil
}
type GetTableRequest struct {
// The unique name of the requested table.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *GetTableRequest) Reset() { *m = GetTableRequest{} }
func (m *GetTableRequest) String() string { return proto.CompactTextString(m) }
func (*GetTableRequest) ProtoMessage() {}
type DeleteTableRequest struct {
// The unique name of the table to be deleted.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *DeleteTableRequest) Reset() { *m = DeleteTableRequest{} }
func (m *DeleteTableRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteTableRequest) ProtoMessage() {}
type RenameTableRequest struct {
// The current unique name of the table.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// The new name by which the table should be referred to within its containing
// cluster, e.g. "foobar" rather than "<cluster_name>/tables/foobar".
NewId string `protobuf:"bytes,2,opt,name=new_id" json:"new_id,omitempty"`
}
func (m *RenameTableRequest) Reset() { *m = RenameTableRequest{} }
func (m *RenameTableRequest) String() string { return proto.CompactTextString(m) }
func (*RenameTableRequest) ProtoMessage() {}
type CreateColumnFamilyRequest struct {
// The unique name of the table in which to create the new column family.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// The name by which the new column family should be referred to within the
// table, e.g. "foobar" rather than "<table_name>/columnFamilies/foobar".
ColumnFamilyId string `protobuf:"bytes,2,opt,name=column_family_id" json:"column_family_id,omitempty"`
// The column family to create. The `name` field must be left blank.
ColumnFamily *google_bigtable_admin_table_v11.ColumnFamily `protobuf:"bytes,3,opt,name=column_family" json:"column_family,omitempty"`
}
func (m *CreateColumnFamilyRequest) Reset() { *m = CreateColumnFamilyRequest{} }
func (m *CreateColumnFamilyRequest) String() string { return proto.CompactTextString(m) }
func (*CreateColumnFamilyRequest) ProtoMessage() {}
func (m *CreateColumnFamilyRequest) GetColumnFamily() *google_bigtable_admin_table_v11.ColumnFamily {
if m != nil {
return m.ColumnFamily
}
return nil
}
type DeleteColumnFamilyRequest struct {
// The unique name of the column family to be deleted.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *DeleteColumnFamilyRequest) Reset() { *m = DeleteColumnFamilyRequest{} }
func (m *DeleteColumnFamilyRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteColumnFamilyRequest) ProtoMessage() {}

View file

@ -0,0 +1,101 @@
// Copyright (c) 2015, Google Inc.
//
// 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.
syntax = "proto3";
package google.bigtable.admin.table.v1;
import "google.golang.org/cloud/bigtable/internal/table_data_proto/bigtable_table_data.proto";
option java_multiple_files = true;
option java_outer_classname = "BigtableTableServiceMessagesProto";
option java_package = "com.google.bigtable.admin.table.v1";
message CreateTableRequest {
// The unique name of the cluster in which to create the new table.
string name = 1;
// The name by which the new table should be referred to within the cluster,
// e.g. "foobar" rather than "<cluster_name>/tables/foobar".
string table_id = 2;
// The Table to create. The `name` field of the Table and all of its
// ColumnFamilies must be left blank, and will be populated in the response.
Table table = 3;
// The optional list of row keys that will be used to initially split the
// table into several tablets (Tablets are similar to HBase regions).
// Given two split keys, "s1" and "s2", three tablets will be created,
// spanning the key ranges: [, s1), [s1, s2), [s2, ).
//
// Example:
// * Row keys := ["a", "apple", "custom", "customer_1", "customer_2",
// "other", "zz"]
// * initial_split_keys := ["apple", "customer_1", "customer_2", "other"]
// * Key assignment:
// - Tablet 1 [, apple) => {"a"}.
// - Tablet 2 [apple, customer_1) => {"apple", "custom"}.
// - Tablet 3 [customer_1, customer_2) => {"customer_1"}.
// - Tablet 4 [customer_2, other) => {"customer_2"}.
// - Tablet 5 [other, ) => {"other", "zz"}.
repeated string initial_split_keys = 4;
}
message ListTablesRequest {
// The unique name of the cluster for which tables should be listed.
string name = 1;
}
message ListTablesResponse {
// The tables present in the requested cluster.
// At present, only the names of the tables are populated.
repeated Table tables = 1;
}
message GetTableRequest {
// The unique name of the requested table.
string name = 1;
}
message DeleteTableRequest {
// The unique name of the table to be deleted.
string name = 1;
}
message RenameTableRequest {
// The current unique name of the table.
string name = 1;
// The new name by which the table should be referred to within its containing
// cluster, e.g. "foobar" rather than "<cluster_name>/tables/foobar".
string new_id = 2;
}
message CreateColumnFamilyRequest {
// The unique name of the table in which to create the new column family.
string name = 1;
// The name by which the new column family should be referred to within the
// table, e.g. "foobar" rather than "<table_name>/columnFamilies/foobar".
string column_family_id = 2;
// The column family to create. The `name` field must be left blank.
ColumnFamily column_family = 3;
}
message DeleteColumnFamilyRequest {
// The unique name of the column family to be deleted.
string name = 1;
}

49
vendor/google.golang.org/cloud/cloud.go generated vendored Normal file
View file

@ -0,0 +1,49 @@
// Copyright 2014 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 cloud contains Google Cloud Platform APIs related types
// and common functions.
package cloud // import "google.golang.org/cloud"
import (
"net/http"
"golang.org/x/net/context"
"google.golang.org/cloud/internal"
)
// NewContext returns a new context that uses the provided http.Client.
// Provided http.Client is responsible to authorize and authenticate
// the requests made to the Google Cloud APIs.
// It mutates the client's original Transport to append the cloud
// package's user-agent to the outgoing requests.
// You can obtain the project ID from the Google Developers Console,
// https://console.developers.google.com.
func NewContext(projID string, c *http.Client) context.Context {
if c == nil {
panic("invalid nil *http.Client passed to NewContext")
}
return WithContext(context.Background(), projID, c)
}
// WithContext returns a new context in a similar way NewContext does,
// but initiates the new context with the specified parent.
func WithContext(parent context.Context, projID string, c *http.Client) context.Context {
// TODO(bradfitz): delete internal.Transport. It's too wrappy for what it does.
// Do User-Agent some other way.
if _, ok := c.Transport.(*internal.Transport); !ok {
c.Transport = &internal.Transport{Base: c.Transport}
}
return internal.WithContext(parent, projID, c)
}

36
vendor/google.golang.org/cloud/cloud_test.go generated vendored Normal file
View file

@ -0,0 +1,36 @@
// Copyright 2014 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 cloud
import (
"net/http"
"testing"
"google.golang.org/cloud/internal"
)
func TestClientTransportMutate(t *testing.T) {
c := &http.Client{Transport: http.DefaultTransport}
NewContext("project-id", c)
NewContext("project-id", c)
tr, ok := c.Transport.(*internal.Transport)
if !ok {
t.Errorf("Transport is expected to be an internal.Transport, found to be a %T", c.Transport)
}
if _, ok := tr.Base.(*internal.Transport); ok {
t.Errorf("Transport's Base shouldn't have been an internal.Transport, found to be a %T", tr.Base)
}
}

View file

@ -0,0 +1,327 @@
// Copyright 2014 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 metadata provides access to Google Compute Engine (GCE)
// metadata and API service accounts.
//
// This package is a wrapper around the GCE metadata service,
// as documented at https://developers.google.com/compute/docs/metadata.
package metadata // import "google.golang.org/cloud/compute/metadata"
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"strings"
"sync"
"time"
"google.golang.org/cloud/internal"
)
type cachedValue struct {
k string
trim bool
mu sync.Mutex
v string
}
var (
projID = &cachedValue{k: "project/project-id", trim: true}
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
instID = &cachedValue{k: "instance/id", trim: true}
)
var metaClient = &http.Client{
Transport: &internal.Transport{
Base: &http.Transport{
Dial: (&net.Dialer{
Timeout: 750 * time.Millisecond,
KeepAlive: 30 * time.Second,
}).Dial,
ResponseHeaderTimeout: 750 * time.Millisecond,
},
},
}
// NotDefinedError is returned when requested metadata is not defined.
//
// The underlying string is the suffix after "/computeMetadata/v1/".
//
// This error is not returned if the value is defined to be the empty
// string.
type NotDefinedError string
func (suffix NotDefinedError) Error() string {
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
}
// Get returns a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
//
// If the GCE_METADATA_HOST environment variable is not defined, a default of
// 169.254.169.254 will be used instead.
//
// If the requested metadata is not defined, the returned error will
// be of type NotDefinedError.
func Get(suffix string) (string, error) {
val, _, err := getETag(suffix)
return val, err
}
// getETag returns a value from the metadata service as well as the associated
// ETag. This func is otherwise equivalent to Get.
func getETag(suffix string) (value, etag string, err error) {
// Using a fixed IP makes it very difficult to spoof the metadata service in
// a container, which is an important use-case for local testing of cloud
// deployments. To enable spoofing of the metadata service, the environment
// variable GCE_METADATA_HOST is first inspected to decide where metadata
// requests shall go.
host := os.Getenv("GCE_METADATA_HOST")
if host == "" {
// Using 169.254.169.254 instead of "metadata" here because Go
// binaries built with the "netgo" tag and without cgo won't
// know the search suffix for "metadata" is
// ".google.internal", and this IP address is documented as
// being stable anyway.
host = "169.254.169.254"
}
url := "http://" + host + "/computeMetadata/v1/" + suffix
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Metadata-Flavor", "Google")
res, err := metaClient.Do(req)
if err != nil {
return "", "", err
}
defer res.Body.Close()
if res.StatusCode == http.StatusNotFound {
return "", "", NotDefinedError(suffix)
}
if res.StatusCode != 200 {
return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
}
all, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}
return string(all), res.Header.Get("Etag"), nil
}
func getTrimmed(suffix string) (s string, err error) {
s, err = Get(suffix)
s = strings.TrimSpace(s)
return
}
func (c *cachedValue) get() (v string, err error) {
defer c.mu.Unlock()
c.mu.Lock()
if c.v != "" {
return c.v, nil
}
if c.trim {
v, err = getTrimmed(c.k)
} else {
v, err = Get(c.k)
}
if err == nil {
c.v = v
}
return
}
var onGCE struct {
sync.Mutex
set bool
v bool
}
// OnGCE reports whether this process is running on Google Compute Engine.
func OnGCE() bool {
defer onGCE.Unlock()
onGCE.Lock()
if onGCE.set {
return onGCE.v
}
onGCE.set = true
// We use the DNS name of the metadata service here instead of the IP address
// because we expect that to fail faster in the not-on-GCE case.
res, err := metaClient.Get("http://metadata.google.internal")
if err != nil {
return false
}
onGCE.v = res.Header.Get("Metadata-Flavor") == "Google"
return onGCE.v
}
// Subscribe subscribes to a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
//
// Subscribe calls fn with the latest metadata value indicated by the provided
// suffix. If the metadata value is deleted, fn is called with the empty string
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
// is deleted. Subscribe returns the error value returned from the last call to
// fn, which may be nil when ok == false.
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
const failedSubscribeSleep = time.Second * 5
// First check to see if the metadata value exists at all.
val, lastETag, err := getETag(suffix)
if err != nil {
return err
}
if err := fn(val, true); err != nil {
return err
}
ok := true
suffix += "?wait_for_change=true&last_etag="
for {
val, etag, err := getETag(suffix + url.QueryEscape(lastETag))
if err != nil {
if _, deleted := err.(NotDefinedError); !deleted {
time.Sleep(failedSubscribeSleep)
continue // Retry on other errors.
}
ok = false
}
lastETag = etag
if err := fn(val, ok); err != nil || !ok {
return err
}
}
}
// ProjectID returns the current instance's project ID string.
func ProjectID() (string, error) { return projID.get() }
// NumericProjectID returns the current instance's numeric project ID.
func NumericProjectID() (string, error) { return projNum.get() }
// InternalIP returns the instance's primary internal IP address.
func InternalIP() (string, error) {
return getTrimmed("instance/network-interfaces/0/ip")
}
// ExternalIP returns the instance's primary external (public) IP address.
func ExternalIP() (string, error) {
return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
}
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func Hostname() (string, error) {
return getTrimmed("instance/hostname")
}
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func InstanceTags() ([]string, error) {
var s []string
j, err := Get("instance/tags")
if err != nil {
return nil, err
}
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
return nil, err
}
return s, nil
}
// InstanceID returns the current VM's numeric instance ID.
func InstanceID() (string, error) {
return instID.get()
}
// InstanceName returns the current VM's instance ID string.
func InstanceName() (string, error) {
host, err := Hostname()
if err != nil {
return "", err
}
return strings.Split(host, ".")[0], nil
}
// Zone returns the current VM's zone, such as "us-central1-b".
func Zone() (string, error) {
zone, err := getTrimmed("instance/zone")
// zone is of the form "projects/<projNum>/zones/<zoneName>".
if err != nil {
return "", err
}
return zone[strings.LastIndex(zone, "/")+1:], nil
}
// InstanceAttributes returns the list of user-defined attributes,
// assigned when initially creating a GCE VM instance. The value of an
// attribute can be obtained with InstanceAttributeValue.
func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }
// ProjectAttributes returns the list of user-defined attributes
// applying to the project as a whole, not just this VM. The value of
// an attribute can be obtained with ProjectAttributeValue.
func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }
func lines(suffix string) ([]string, error) {
j, err := Get(suffix)
if err != nil {
return nil, err
}
s := strings.Split(strings.TrimSpace(j), "\n")
for i := range s {
s[i] = strings.TrimSpace(s[i])
}
return s, nil
}
// InstanceAttributeValue returns the value of the provided VM
// instance attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// InstanceAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func InstanceAttributeValue(attr string) (string, error) {
return Get("instance/attributes/" + attr)
}
// ProjectAttributeValue returns the value of the provided
// project attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// ProjectAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func ProjectAttributeValue(attr string) (string, error) {
return Get("project/attributes/" + attr)
}
// Scopes returns the service account scopes for the given account.
// The account may be empty or the string "default" to use the instance's
// main account.
func Scopes(serviceAccount string) ([]string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return lines("instance/service-accounts/" + serviceAccount + "/scopes")
}

251
vendor/google.golang.org/cloud/container/container.go generated vendored Normal file
View file

@ -0,0 +1,251 @@
// Copyright 2014 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 container contains a Google Container Engine client.
//
// For more information about the API,
// see https://cloud.google.com/container-engine/docs
package container // import "google.golang.org/cloud/container"
import (
"errors"
"net/http"
"time"
"golang.org/x/net/context"
raw "google.golang.org/api/container/v1"
"google.golang.org/cloud/internal"
)
type Type string
var (
TypeCreate Type = Type("createCluster")
TypeDelete Type = Type("deleteCluster")
)
type Status string
var (
Done = Status("done")
Pending = Status("pending")
Running = Status("running")
Error = Status("error")
Provisioning = Status("provisioning")
Stopping = Status("stopping")
)
// Resource is a Google Container Engine cluster resource.
type Resource struct {
// Name is the name of this cluster. The name must be unique
// within this project and zone, and can be up to 40 characters.
Name string
// Description is the description of the cluster. Optional.
Description string
// Zone is the Google Compute Engine zone in which the cluster resides.
Zone string
// Status is the current status of the cluster. It could either be
// StatusError, StatusProvisioning, StatusRunning or StatusStopping.
Status Status
// Num is the number of the nodes in this cluster resource.
Num int64
// APIVersion is the version of the Kubernetes master and kubelets running
// in this cluster. Allowed value is 0.4.2, or leave blank to
// pick up the latest stable release.
APIVersion string
// Endpoint is the IP address of this cluster's Kubernetes master.
// The endpoint can be accessed at https://username:password@endpoint/.
// See Username and Password fields for the username and password information.
Endpoint string
// Username is the username to use when accessing the Kubernetes master endpoint.
Username string
// Password is the password to use when accessing the Kubernetes master endpoint.
Password string
// ContainerIPv4CIDR is the IP addresses of the container pods in
// this cluster, in CIDR notation (e.g. 1.2.3.4/29).
ContainerIPv4CIDR string
// ServicesIPv4CIDR is the IP addresses of the Kubernetes services in this
// cluster, in CIDR notation (e.g. 1.2.3.4/29). Service addresses are
// always in the 10.0.0.0/16 range.
ServicesIPv4CIDR string
// MachineType is a Google Compute Engine machine type (e.g. n1-standard-1).
// If none set, the default type is used while creating a new cluster.
MachineType string
// This field is ignored. It was removed from the underlying container API in v1.
SourceImage string
// Created is the creation time of this cluster.
Created time.Time
}
func resourceFromRaw(c *raw.Cluster) *Resource {
if c == nil {
return nil
}
r := &Resource{
Name: c.Name,
Description: c.Description,
Zone: c.Zone,
Status: Status(c.Status),
Num: c.InitialNodeCount,
APIVersion: c.InitialClusterVersion,
Endpoint: c.Endpoint,
Username: c.MasterAuth.Username,
Password: c.MasterAuth.Password,
ContainerIPv4CIDR: c.ClusterIpv4Cidr,
ServicesIPv4CIDR: c.ServicesIpv4Cidr,
MachineType: c.NodeConfig.MachineType,
}
r.Created, _ = time.Parse(time.RFC3339, c.CreateTime)
return r
}
func resourcesFromRaw(c []*raw.Cluster) []*Resource {
r := make([]*Resource, len(c))
for i, val := range c {
r[i] = resourceFromRaw(val)
}
return r
}
// Op represents a Google Container Engine API operation.
type Op struct {
// Name is the name of the operation.
Name string
// Zone is the Google Compute Engine zone.
Zone string
// This field is ignored. It was removed from the underlying container API in v1.
TargetURL string
// Type is the operation type. It could be either be TypeCreate or TypeDelete.
Type Type
// Status is the current status of this operation. It could be either
// OpDone or OpPending.
Status Status
}
func opFromRaw(o *raw.Operation) *Op {
if o == nil {
return nil
}
return &Op{
Name: o.Name,
Zone: o.Zone,
Type: Type(o.OperationType),
Status: Status(o.Status),
}
}
func opsFromRaw(o []*raw.Operation) []*Op {
ops := make([]*Op, len(o))
for i, val := range o {
ops[i] = opFromRaw(val)
}
return ops
}
// Clusters returns a list of cluster resources from the specified zone.
// If no zone is specified, it returns all clusters under the user project.
func Clusters(ctx context.Context, zone string) ([]*Resource, error) {
s := rawService(ctx)
if zone == "" {
resp, err := s.Projects.Zones.Clusters.List(internal.ProjID(ctx), "-").Do()
if err != nil {
return nil, err
}
return resourcesFromRaw(resp.Clusters), nil
}
resp, err := s.Projects.Zones.Clusters.List(internal.ProjID(ctx), zone).Do()
if err != nil {
return nil, err
}
return resourcesFromRaw(resp.Clusters), nil
}
// Cluster returns metadata about the specified cluster.
func Cluster(ctx context.Context, zone, name string) (*Resource, error) {
s := rawService(ctx)
resp, err := s.Projects.Zones.Clusters.Get(internal.ProjID(ctx), zone, name).Do()
if err != nil {
return nil, err
}
return resourceFromRaw(resp), nil
}
// CreateCluster creates a new cluster with the provided metadata
// in the specified zone.
func CreateCluster(ctx context.Context, zone string, resource *Resource) (*Resource, error) {
panic("not implemented")
}
// DeleteCluster deletes a cluster.
func DeleteCluster(ctx context.Context, zone, name string) error {
s := rawService(ctx)
_, err := s.Projects.Zones.Clusters.Delete(internal.ProjID(ctx), zone, name).Do()
return err
}
// Operations returns a list of operations from the specified zone.
// If no zone is specified, it looks up for all of the operations
// that are running under the user's project.
func Operations(ctx context.Context, zone string) ([]*Op, error) {
s := rawService(ctx)
if zone == "" {
resp, err := s.Projects.Zones.Operations.List(internal.ProjID(ctx), "-").Do()
if err != nil {
return nil, err
}
return opsFromRaw(resp.Operations), nil
}
resp, err := s.Projects.Zones.Operations.List(internal.ProjID(ctx), zone).Do()
if err != nil {
return nil, err
}
return opsFromRaw(resp.Operations), nil
}
// Operation returns an operation.
func Operation(ctx context.Context, zone, name string) (*Op, error) {
s := rawService(ctx)
resp, err := s.Projects.Zones.Operations.Get(internal.ProjID(ctx), zone, name).Do()
if err != nil {
return nil, err
}
if resp.StatusMessage != "" {
return nil, errors.New(resp.StatusMessage)
}
return opFromRaw(resp), nil
}
func rawService(ctx context.Context) *raw.Service {
return internal.Service(ctx, "container", func(hc *http.Client) interface{} {
svc, _ := raw.New(hc)
return svc
}).(*raw.Service)
}

496
vendor/google.golang.org/cloud/datastore/datastore.go generated vendored Normal file
View file

@ -0,0 +1,496 @@
// Copyright 2014 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 contains a Google Cloud Datastore client.
//
// This package is experimental and may make backwards-incompatible changes.
package datastore // import "google.golang.org/cloud/datastore"
import (
"errors"
"fmt"
"reflect"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"google.golang.org/cloud"
pb "google.golang.org/cloud/internal/datastore"
"google.golang.org/cloud/internal/transport"
)
const prodAddr = "https://www.googleapis.com/datastore/v1beta2/datasets/"
const userAgent = "gcloud-golang-datastore/20150727"
const (
// ScopeDatastore grants permissions to view and/or manage datastore entities
ScopeDatastore = "https://www.googleapis.com/auth/datastore"
// ScopeUserEmail grants permission to view the user's email address.
// It is required to access the datastore.
ScopeUserEmail = "https://www.googleapis.com/auth/userinfo.email"
)
// protoClient is an interface for *transport.ProtoClient to support injecting
// fake clients in tests.
type protoClient interface {
Call(context.Context, string, proto.Message, proto.Message) error
}
// Client is a client for reading and writing data in a datastore dataset.
type Client struct {
client protoClient
endpoint string
dataset string // Called dataset by the datastore API, synonym for project ID.
}
// NewClient creates a new Client for a given dataset.
func NewClient(ctx context.Context, projectID string, opts ...cloud.ClientOption) (*Client, error) {
o := []cloud.ClientOption{
cloud.WithEndpoint(prodAddr),
cloud.WithScopes(ScopeDatastore, ScopeUserEmail),
cloud.WithUserAgent(userAgent),
}
o = append(o, opts...)
client, err := transport.NewProtoClient(ctx, o...)
if err != nil {
return nil, fmt.Errorf("dialing: %v", err)
}
return &Client{
client: client,
dataset: projectID,
}, nil
}
var (
// ErrInvalidEntityType is returned when functions like Get or Next are
// passed a dst or src argument of invalid type.
ErrInvalidEntityType = errors.New("datastore: invalid entity type")
// ErrInvalidKey is returned when an invalid key is presented.
ErrInvalidKey = errors.New("datastore: invalid key")
// ErrNoSuchEntity is returned when no entity was found for a given key.
ErrNoSuchEntity = errors.New("datastore: no such entity")
)
type multiArgType int
const (
multiArgTypeInvalid multiArgType = iota
multiArgTypePropertyLoadSaver
multiArgTypeStruct
multiArgTypeStructPtr
multiArgTypeInterface
)
// nsKey is the type of the context.Context key to store the datastore
// namespace.
type nsKey struct{}
// WithNamespace returns a new context that limits the scope its parent
// context with a Datastore namespace.
func WithNamespace(parent context.Context, namespace string) context.Context {
return context.WithValue(parent, nsKey{}, namespace)
}
// ctxNamespace returns the active namespace for a context.
// It defaults to "" if no namespace was specified.
func ctxNamespace(ctx context.Context) string {
v, _ := ctx.Value(nsKey{}).(string)
return v
}
// ErrFieldMismatch is returned when a field is to be loaded into a different
// type than the one it was stored from, or when a field is missing or
// unexported in the destination struct.
// StructType is the type of the struct pointed to by the destination argument
// passed to Get or to Iterator.Next.
type ErrFieldMismatch struct {
StructType reflect.Type
FieldName string
Reason string
}
func (e *ErrFieldMismatch) Error() string {
return fmt.Sprintf("datastore: cannot load field %q into a %q: %s",
e.FieldName, e.StructType, e.Reason)
}
func (c *Client) call(ctx context.Context, method string, req, resp proto.Message) error {
return c.client.Call(ctx, c.dataset+"/"+method, req, resp)
}
func keyToProto(k *Key) *pb.Key {
if k == nil {
return nil
}
// TODO(jbd): Eliminate unrequired allocations.
path := []*pb.Key_PathElement(nil)
for {
el := &pb.Key_PathElement{
Kind: proto.String(k.kind),
}
if k.id != 0 {
el.Id = proto.Int64(k.id)
}
if k.name != "" {
el.Name = proto.String(k.name)
}
path = append([]*pb.Key_PathElement{el}, path...)
if k.parent == nil {
break
}
k = k.parent
}
key := &pb.Key{
PathElement: path,
}
if k.namespace != "" {
key.PartitionId = &pb.PartitionId{
Namespace: proto.String(k.namespace),
}
}
return key
}
func protoToKey(p *pb.Key) *Key {
keys := make([]*Key, len(p.GetPathElement()))
for i, el := range p.GetPathElement() {
keys[i] = &Key{
namespace: p.GetPartitionId().GetNamespace(),
kind: el.GetKind(),
id: el.GetId(),
name: el.GetName(),
}
}
for i := 0; i < len(keys)-1; i++ {
keys[i+1].parent = keys[i]
}
return keys[len(keys)-1]
}
// multiKeyToProto is a batch version of keyToProto.
func multiKeyToProto(keys []*Key) []*pb.Key {
ret := make([]*pb.Key, len(keys))
for i, k := range keys {
ret[i] = keyToProto(k)
}
return ret
}
// multiKeyToProto is a batch version of keyToProto.
func multiProtoToKey(keys []*pb.Key) []*Key {
ret := make([]*Key, len(keys))
for i, k := range keys {
ret[i] = protoToKey(k)
}
return ret
}
// multiValid is a batch version of Key.valid. It returns an error, not a
// []bool.
func multiValid(key []*Key) error {
invalid := false
for _, k := range key {
if !k.valid() {
invalid = true
break
}
}
if !invalid {
return nil
}
err := make(MultiError, len(key))
for i, k := range key {
if !k.valid() {
err[i] = ErrInvalidKey
}
}
return err
}
// checkMultiArg checks that v has type []S, []*S, []I, or []P, for some struct
// type S, for some interface type I, or some non-interface non-pointer type P
// such that P or *P implements PropertyLoadSaver.
//
// It returns what category the slice's elements are, and the reflect.Type
// that represents S, I or P.
//
// As a special case, PropertyList is an invalid type for v.
func checkMultiArg(v reflect.Value) (m multiArgType, elemType reflect.Type) {
if v.Kind() != reflect.Slice {
return multiArgTypeInvalid, nil
}
if v.Type() == typeOfPropertyList {
return multiArgTypeInvalid, nil
}
elemType = v.Type().Elem()
if reflect.PtrTo(elemType).Implements(typeOfPropertyLoadSaver) {
return multiArgTypePropertyLoadSaver, elemType
}
switch elemType.Kind() {
case reflect.Struct:
return multiArgTypeStruct, elemType
case reflect.Interface:
return multiArgTypeInterface, elemType
case reflect.Ptr:
elemType = elemType.Elem()
if elemType.Kind() == reflect.Struct {
return multiArgTypeStructPtr, elemType
}
}
return multiArgTypeInvalid, nil
}
// Get loads the entity stored for key into dst, which must be a struct pointer
// or implement PropertyLoadSaver. If there is no such entity for the key, Get
// returns ErrNoSuchEntity.
//
// The values of dst's unmatched struct fields are not modified, and matching
// slice-typed fields are not reset before appending to them. In particular, it
// is recommended to pass a pointer to a zero valued struct on each Get call.
//
// ErrFieldMismatch is returned when a field is to be loaded into a different
// 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 {
err := c.get(ctx, []*Key{key}, []interface{}{dst}, nil)
if me, ok := err.(MultiError); ok {
return me[0]
}
return err
}
// GetMulti is a batch version of Get.
//
// dst must be a []S, []*S, []I or []P, for some struct type S, some interface
// type I, or some non-interface non-pointer type P such that P or *P
// implements PropertyLoadSaver. If an []I, each element must be a valid dst
// for Get: it must be a struct pointer or implement PropertyLoadSaver.
//
// 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 {
return c.get(ctx, keys, dst, nil)
}
func (c *Client) get(ctx context.Context, keys []*Key, dst interface{}, opts *pb.ReadOptions) error {
v := reflect.ValueOf(dst)
multiArgType, _ := checkMultiArg(v)
// Sanity checks
if multiArgType == multiArgTypeInvalid {
return errors.New("datastore: dst has invalid type")
}
if len(keys) != v.Len() {
return errors.New("datastore: keys and dst slices have different length")
}
if len(keys) == 0 {
return nil
}
// Go through keys, validate them, serialize then, and create a dict mapping them to their index
multiErr, any := make(MultiError, len(keys)), false
keyMap := make(map[string]int)
pbKeys := make([]*pb.Key, len(keys))
for i, k := range keys {
if !k.valid() {
multiErr[i] = ErrInvalidKey
any = true
} else {
keyMap[k.String()] = i
pbKeys[i] = keyToProto(k)
}
}
if any {
return multiErr
}
req := &pb.LookupRequest{
Key: pbKeys,
ReadOptions: opts,
}
resp := &pb.LookupResponse{}
if err := c.call(ctx, "lookup", req, resp); err != nil {
return err
}
if len(resp.Deferred) > 0 {
// TODO(jbd): Assess whether we should retry the deferred keys.
return errors.New("datastore: some entities temporarily unavailable")
}
if len(keys) != len(resp.Found)+len(resp.Missing) {
return errors.New("datastore: internal error: server returned the wrong number of entities")
}
for _, e := range resp.Found {
k := protoToKey(e.Entity.Key)
index := keyMap[k.String()]
elem := v.Index(index)
if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct {
elem = elem.Addr()
}
err := loadEntity(elem.Interface(), e.Entity)
if err != nil {
multiErr[index] = err
any = true
}
}
for _, e := range resp.Missing {
k := protoToKey(e.Entity.Key)
multiErr[keyMap[k.String()]] = ErrNoSuchEntity
any = true
}
if any {
return multiErr
}
return nil
}
// Put saves the entity src into the datastore with key k. src must be a struct
// pointer or implement PropertyLoadSaver; if a struct pointer then any
// unexported fields of that struct will be skipped. If k is an incomplete key,
// the returned key will be a unique key generated by the datastore.
func (c *Client) Put(ctx context.Context, key *Key, src interface{}) (*Key, error) {
k, err := c.PutMulti(ctx, []*Key{key}, []interface{}{src})
if err != nil {
if me, ok := err.(MultiError); ok {
return nil, me[0]
}
return nil, err
}
return k[0], nil
}
// 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) {
mutation, err := putMutation(keys, src)
if err != nil {
return nil, err
}
// Make the request.
req := &pb.CommitRequest{
Mutation: mutation,
Mode: pb.CommitRequest_NON_TRANSACTIONAL.Enum(),
}
resp := &pb.CommitResponse{}
if err := c.call(ctx, "commit", req, resp); err != nil {
return nil, err
}
// Copy any newly minted keys into the returned keys.
newKeys := make(map[int]int) // Map of index in returned slice to index in response.
ret := make([]*Key, len(keys))
var idx int
for i, key := range keys {
if key.Incomplete() {
// This key will be in the mutation result.
newKeys[i] = idx
idx++
} else {
ret[i] = key
}
}
if len(newKeys) != len(resp.MutationResult.InsertAutoIdKey) {
return nil, errors.New("datastore: internal error: server returned the wrong number of keys")
}
for retI, respI := range newKeys {
ret[retI] = protoToKey(resp.MutationResult.InsertAutoIdKey[respI])
}
return ret, nil
}
func putMutation(keys []*Key, src interface{}) (*pb.Mutation, error) {
v := reflect.ValueOf(src)
multiArgType, _ := checkMultiArg(v)
if multiArgType == multiArgTypeInvalid {
return nil, errors.New("datastore: src has invalid type")
}
if len(keys) != v.Len() {
return nil, errors.New("datastore: key and src slices have different length")
}
if len(keys) == 0 {
return nil, nil
}
if err := multiValid(keys); err != nil {
return nil, err
}
var upsert, insert []*pb.Entity
for i, k := range keys {
val := reflect.ValueOf(src).Index(i)
// If src is an interface slice []interface{}{ent1, ent2}
if val.Kind() == reflect.Interface && val.Elem().Kind() == reflect.Slice {
val = val.Elem()
}
// If src is a slice of ptrs []*T{ent1, ent2}
if val.Kind() == reflect.Ptr && val.Elem().Kind() == reflect.Slice {
val = val.Elem()
}
p, err := saveEntity(k, val.Interface())
if err != nil {
return nil, fmt.Errorf("datastore: Error while saving %v: %v", k.String(), err)
}
if k.Incomplete() {
insert = append(insert, p)
} else {
upsert = append(upsert, p)
}
}
return &pb.Mutation{
InsertAutoId: insert,
Upsert: upsert,
}, nil
}
// Delete deletes the entity for the given key.
func (c *Client) Delete(ctx context.Context, key *Key) error {
err := c.DeleteMulti(ctx, []*Key{key})
if me, ok := err.(MultiError); ok {
return me[0]
}
return err
}
// DeleteMulti is a batch version of Delete.
func (c *Client) DeleteMulti(ctx context.Context, keys []*Key) error {
mutation, err := deleteMutation(keys)
if err != nil {
return err
}
req := &pb.CommitRequest{
Mutation: mutation,
Mode: pb.CommitRequest_NON_TRANSACTIONAL.Enum(),
}
resp := &pb.CommitResponse{}
return c.call(ctx, "commit", req, resp)
}
func deleteMutation(keys []*Key) (*pb.Mutation, error) {
protoKeys := make([]*pb.Key, len(keys))
for i, k := range keys {
if k.Incomplete() {
return nil, fmt.Errorf("datastore: can't delete the incomplete key: %v", k)
}
protoKeys[i] = keyToProto(k)
}
return &pb.Mutation{
Delete: protoKeys,
}, nil
}

File diff suppressed because it is too large Load diff

47
vendor/google.golang.org/cloud/datastore/errors.go generated vendored Normal file
View file

@ -0,0 +1,47 @@
// Copyright 2014 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.
// This file provides error functions for common API failure modes.
package datastore
import (
"fmt"
)
// MultiError is returned by batch operations when there are errors with
// particular elements. Errors will be in a one-to-one correspondence with
// the input elements; successful elements will have a nil entry.
type MultiError []error
func (m MultiError) Error() string {
s, n := "", 0
for _, e := range m {
if e != nil {
if n == 0 {
s = e.Error()
}
n++
}
}
switch n {
case 0:
return "(0 errors)"
case 1:
return s
case 2:
return s + " (and 1 other error)"
}
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
}

View file

@ -0,0 +1,251 @@
// Copyright 2014 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_test
import (
"io/ioutil"
"log"
"time"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"google.golang.org/cloud"
"google.golang.org/cloud/datastore"
)
// TODO(djd): reevaluate this example given new Client config.
func Example_auth() *datastore.Client {
// Initialize an authorized context with Google Developers Console
// JSON key. Read the google package examples to learn more about
// different authorization flows you can use.
// http://godoc.org/golang.org/x/oauth2/google
jsonKey, err := ioutil.ReadFile("/path/to/json/keyfile.json")
if err != nil {
log.Fatal(err)
}
conf, err := google.JWTConfigFromJSON(
jsonKey,
datastore.ScopeDatastore,
datastore.ScopeUserEmail,
)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id", cloud.WithTokenSource(conf.TokenSource(ctx)))
if err != nil {
log.Fatal(err)
}
// Use the client (see other examples).
return client
}
func ExampleGet() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
}
type Article struct {
Title string
Description string
Body string `datastore:",noindex"`
Author *datastore.Key
PublishedAt time.Time
}
key := datastore.NewKey(ctx, "Article", "articled1", 0, nil)
article := &Article{}
if err := client.Get(ctx, key, article); err != nil {
log.Fatal(err)
}
}
func ExamplePut() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
}
type Article struct {
Title string
Description string
Body string `datastore:",noindex"`
Author *datastore.Key
PublishedAt time.Time
}
newKey := datastore.NewIncompleteKey(ctx, "Article", nil)
_, err = client.Put(ctx, newKey, &Article{
Title: "The title of the article",
Description: "The description of the article...",
Body: "...",
Author: datastore.NewKey(ctx, "Author", "jbd", 0, nil),
PublishedAt: time.Now(),
})
if err != nil {
log.Fatal(err)
}
}
func ExampleDelete() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
}
key := datastore.NewKey(ctx, "Article", "articled1", 0, nil)
if err := client.Delete(ctx, key); err != nil {
log.Fatal(err)
}
}
type Post struct {
Title string
PublishedAt time.Time
Comments int
}
func ExampleGetMulti() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
}
keys := []*datastore.Key{
datastore.NewKey(ctx, "Post", "post1", 0, nil),
datastore.NewKey(ctx, "Post", "post2", 0, nil),
datastore.NewKey(ctx, "Post", "post3", 0, nil),
}
posts := make([]Post, 3)
if err := client.GetMulti(ctx, keys, posts); err != nil {
log.Println(err)
}
}
func ExamplePutMulti_slice() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
}
keys := []*datastore.Key{
datastore.NewKey(ctx, "Post", "post1", 0, nil),
datastore.NewKey(ctx, "Post", "post2", 0, nil),
}
// PutMulti with a Post slice.
posts := []*Post{
{Title: "Post 1", PublishedAt: time.Now()},
{Title: "Post 2", PublishedAt: time.Now()},
}
if _, err := client.PutMulti(ctx, keys, posts); err != nil {
log.Fatal(err)
}
}
func ExamplePutMulti_interfaceSlice() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
}
keys := []*datastore.Key{
datastore.NewKey(ctx, "Post", "post1", 0, nil),
datastore.NewKey(ctx, "Post", "post2", 0, nil),
}
// PutMulti with an empty interface slice.
posts := []interface{}{
&Post{Title: "Post 1", PublishedAt: time.Now()},
&Post{Title: "Post 2", PublishedAt: time.Now()},
}
if _, err := client.PutMulti(ctx, keys, posts); err != nil {
log.Fatal(err)
}
}
func ExampleQuery() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
}
// Count the number of the post entities.
q := datastore.NewQuery("Post")
n, err := client.Count(ctx, q)
if err != nil {
log.Fatal(err)
}
log.Println("There are %d posts.", n)
// List the posts published since yesterday.
yesterday := time.Now().Add(-24 * time.Hour)
q = datastore.NewQuery("Post").Filter("PublishedAt >", yesterday)
it := client.Run(ctx, q)
// Use the iterator.
_ = it
// Order the posts by the number of comments they have recieved.
datastore.NewQuery("Post").Order("-Comments")
// Start listing from an offset and limit the results.
datastore.NewQuery("Post").Offset(20).Limit(10)
}
func ExampleTransaction() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
}
const retries = 3
// Increment a counter.
// See https://cloud.google.com/appengine/articles/sharding_counters for
// a more scalable solution.
type Counter struct {
Count int
}
key := datastore.NewKey(ctx, "counter", "CounterA", 0, nil)
for i := 0; i < retries; i++ {
tx, err := client.NewTransaction(ctx)
if err != nil {
break
}
var c Counter
if err := tx.Get(key, &c); err != nil && err != datastore.ErrNoSuchEntity {
break
}
c.Count++
if _, err := tx.Put(key, &c); err != nil {
break
}
// Attempt to commit the transaction. If there's a conflict, try again.
if _, err := tx.Commit(); err != datastore.ErrConcurrentTransaction {
break
}
}
}

View file

@ -0,0 +1,569 @@
// Copyright 2014 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 integration
package datastore
import (
"fmt"
"log"
"reflect"
"sort"
"strings"
"testing"
"time"
"golang.org/x/net/context"
"google.golang.org/cloud"
"google.golang.org/cloud/internal/testutil"
)
func newClient(ctx context.Context) *Client {
ts := testutil.TokenSource(ctx, ScopeDatastore, ScopeUserEmail)
client, err := NewClient(ctx, testutil.ProjID(), cloud.WithTokenSource(ts))
if err != nil {
log.Fatal(err)
}
return client
}
func TestBasics(t *testing.T) {
type X struct {
I int
S string
T time.Time
}
ctx := context.Background()
client := newClient(ctx)
x0 := X{66, "99", time.Now().Truncate(time.Millisecond)}
k, err := client.Put(ctx, NewIncompleteKey(ctx, "BasicsX", nil), &x0)
if err != nil {
t.Fatalf("client.Put: %v", err)
}
x1 := X{}
err = client.Get(ctx, k, &x1)
if err != nil {
t.Errorf("client.Get: %v", err)
}
err = client.Delete(ctx, k)
if err != nil {
t.Errorf("client.Delete: %v", err)
}
if !reflect.DeepEqual(x0, x1) {
t.Errorf("compare: x0=%v, x1=%v", x0, x1)
}
}
func TestListValues(t *testing.T) {
p0 := PropertyList{
{Name: "L", Value: int64(12), Multiple: true},
{Name: "L", Value: "string", Multiple: true},
{Name: "L", Value: true, Multiple: true},
}
ctx := context.Background()
client := newClient(ctx)
k, err := client.Put(ctx, NewIncompleteKey(ctx, "ListValue", nil), &p0)
if err != nil {
t.Fatalf("client.Put: %v", err)
}
var p1 PropertyList
if err := client.Get(ctx, k, &p1); err != nil {
t.Errorf("client.Get: %v", err)
}
if !reflect.DeepEqual(p0, p1) {
t.Errorf("compare:\np0=%v\np1=%#v", p0, p1)
}
if err = client.Delete(ctx, k); err != nil {
t.Errorf("client.Delete: %v", err)
}
}
func TestGetMulti(t *testing.T) {
type X struct {
I int
}
ctx := context.Background()
client := newClient(ctx)
p := NewKey(ctx, "X", "", time.Now().Unix(), nil)
cases := []struct {
key *Key
put bool
}{
{key: NewKey(ctx, "X", "item1", 0, p), put: true},
{key: NewKey(ctx, "X", "item2", 0, p), put: false},
{key: NewKey(ctx, "X", "item3", 0, p), put: false},
{key: NewKey(ctx, "X", "item4", 0, p), put: true},
}
var src, dst []*X
var srcKeys, dstKeys []*Key
for _, c := range cases {
dst = append(dst, &X{})
dstKeys = append(dstKeys, c.key)
if c.put {
src = append(src, &X{})
srcKeys = append(srcKeys, c.key)
}
}
if _, err := client.PutMulti(ctx, srcKeys, src); err != nil {
t.Error(err)
}
err := client.GetMulti(ctx, dstKeys, dst)
if err == nil {
t.Errorf("client.GetMulti got %v, expected error", err)
}
e, ok := err.(MultiError)
if !ok {
t.Errorf("client.GetMulti got %t, expected MultiError", err)
}
for i, err := range e {
got, want := err, (error)(nil)
if !cases[i].put {
got, want = err, ErrNoSuchEntity
}
if got != want {
t.Errorf("MultiError[%d] == %v, want %v", i, got, want)
}
}
}
type Z struct {
S string
T string `datastore:",noindex"`
P []byte
K []byte `datastore:",noindex"`
}
func (z Z) String() string {
var lens []string
v := reflect.ValueOf(z)
for i := 0; i < v.NumField(); i++ {
if l := v.Field(i).Len(); l > 0 {
lens = append(lens, fmt.Sprintf("len(%s)=%d", v.Type().Field(i).Name, l))
}
}
return fmt.Sprintf("Z{ %s }", strings.Join(lens, ","))
}
func TestUnindexableValues(t *testing.T) {
x1500 := strings.Repeat("x", 1500)
x1501 := strings.Repeat("x", 1501)
testCases := []struct {
in Z
wantErr bool
}{
{in: Z{S: x1500}, wantErr: false},
{in: Z{S: x1501}, wantErr: true},
{in: Z{T: x1500}, wantErr: false},
{in: Z{T: x1501}, wantErr: false},
{in: Z{P: []byte(x1500)}, wantErr: false},
{in: Z{P: []byte(x1501)}, wantErr: true},
{in: Z{K: []byte(x1500)}, wantErr: false},
{in: Z{K: []byte(x1501)}, wantErr: false},
}
ctx := context.Background()
client := newClient(ctx)
for _, tt := range testCases {
_, err := client.Put(ctx, NewIncompleteKey(ctx, "BasicsZ", nil), &tt.in)
if (err != nil) != tt.wantErr {
t.Errorf("client.Put %s got err %v, want err %t", tt.in, err, tt.wantErr)
}
}
}
type SQChild struct {
I, J int
T, U int64
}
type SQTestCase struct {
desc string
q *Query
wantCount int
wantSum int
}
func testSmallQueries(t *testing.T, ctx context.Context, client *Client, parent *Key, children []*SQChild,
testCases []SQTestCase, extraTests ...func()) {
keys := make([]*Key, len(children))
for i := range keys {
keys[i] = NewIncompleteKey(ctx, "SQChild", parent)
}
keys, err := client.PutMulti(ctx, keys, children)
if err != nil {
t.Fatalf("client.PutMulti: %v", err)
}
defer func() {
err := client.DeleteMulti(ctx, keys)
if err != nil {
t.Errorf("client.DeleteMulti: %v", err)
}
}()
for _, tc := range testCases {
count, err := client.Count(ctx, tc.q)
if err != nil {
t.Errorf("Count %q: %v", tc.desc, err)
continue
}
if count != tc.wantCount {
t.Errorf("Count %q: got %d want %d", tc.desc, count, tc.wantCount)
continue
}
}
for _, tc := range testCases {
var got []SQChild
_, err := client.GetAll(ctx, tc.q, &got)
if err != nil {
t.Errorf("client.GetAll %q: %v", tc.desc, err)
continue
}
sum := 0
for _, c := range got {
sum += c.I + c.J
}
if sum != tc.wantSum {
t.Errorf("sum %q: got %d want %d", tc.desc, sum, tc.wantSum)
continue
}
}
for _, x := range extraTests {
x()
}
}
func TestFilters(t *testing.T) {
ctx := context.Background()
client := newClient(ctx)
parent := NewKey(ctx, "SQParent", "TestFilters", 0, nil)
now := time.Now().Truncate(time.Millisecond).Unix()
children := []*SQChild{
{I: 0, T: now, U: now},
{I: 1, T: now, U: now},
{I: 2, T: now, U: now},
{I: 3, T: now, U: now},
{I: 4, T: now, U: now},
{I: 5, T: now, U: now},
{I: 6, T: now, U: now},
{I: 7, T: now, U: now},
}
baseQuery := NewQuery("SQChild").Ancestor(parent).Filter("T=", now)
testSmallQueries(t, ctx, client, parent, children, []SQTestCase{
{
"I>1",
baseQuery.Filter("I>", 1),
6,
2 + 3 + 4 + 5 + 6 + 7,
},
{
"I>2 AND I<=5",
baseQuery.Filter("I>", 2).Filter("I<=", 5),
3,
3 + 4 + 5,
},
{
"I>=3 AND I<3",
baseQuery.Filter("I>=", 3).Filter("I<", 3),
0,
0,
},
{
"I=4",
baseQuery.Filter("I=", 4),
1,
4,
},
}, func() {
got := []*SQChild{}
want := []*SQChild{
{I: 0, T: now, U: now},
{I: 1, T: now, U: now},
{I: 2, T: now, U: now},
{I: 3, T: now, U: now},
{I: 4, T: now, U: now},
{I: 5, T: now, U: now},
{I: 6, T: now, U: now},
{I: 7, T: now, U: now},
}
_, err := client.GetAll(ctx, baseQuery.Order("I"), &got)
if err != nil {
t.Errorf("client.GetAll: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("compare: got=%v, want=%v", got, want)
}
}, func() {
got := []*SQChild{}
want := []*SQChild{
{I: 7, T: now, U: now},
{I: 6, T: now, U: now},
{I: 5, T: now, U: now},
{I: 4, T: now, U: now},
{I: 3, T: now, U: now},
{I: 2, T: now, U: now},
{I: 1, T: now, U: now},
{I: 0, T: now, U: now},
}
_, err := client.GetAll(ctx, baseQuery.Order("-I"), &got)
if err != nil {
t.Errorf("client.GetAll: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("compare: got=%v, want=%v", got, want)
}
})
}
func TestEventualConsistency(t *testing.T) {
ctx := context.Background()
client := newClient(ctx)
parent := NewKey(ctx, "SQParent", "TestEventualConsistency", 0, nil)
now := time.Now().Truncate(time.Millisecond).Unix()
children := []*SQChild{
{I: 0, T: now, U: now},
{I: 1, T: now, U: now},
{I: 2, T: now, U: now},
}
query := NewQuery("SQChild").Ancestor(parent).Filter("T =", now).EventualConsistency()
testSmallQueries(t, ctx, client, parent, children, nil, func() {
got, err := client.Count(ctx, query)
if err != nil {
t.Fatalf("Count: %v", err)
}
if got < 0 || 3 < got {
t.Errorf("Count: got %d, want [0,3]", got)
}
})
}
func TestProjection(t *testing.T) {
ctx := context.Background()
client := newClient(ctx)
parent := NewKey(ctx, "SQParent", "TestProjection", 0, nil)
now := time.Now().Truncate(time.Millisecond).Unix()
children := []*SQChild{
{I: 1 << 0, J: 100, T: now, U: now},
{I: 1 << 1, J: 100, T: now, U: now},
{I: 1 << 2, J: 200, T: now, U: now},
{I: 1 << 3, J: 300, T: now, U: now},
{I: 1 << 4, J: 300, T: now, U: now},
}
baseQuery := NewQuery("SQChild").Ancestor(parent).Filter("T=", now).Filter("J>", 150)
testSmallQueries(t, ctx, client, parent, children, []SQTestCase{
{
"project",
baseQuery.Project("J"),
3,
200 + 300 + 300,
},
{
"distinct",
baseQuery.Project("J").Distinct(),
2,
200 + 300,
},
{
"project on meaningful (GD_WHEN) field",
baseQuery.Project("U"),
3,
0,
},
})
}
func TestAllocateIDs(t *testing.T) {
ctx := context.Background()
client := newClient(ctx)
keys := make([]*Key, 5)
for i := range keys {
keys[i] = NewIncompleteKey(ctx, "AllocID", nil)
}
keys, err := client.AllocateIDs(ctx, keys)
if err != nil {
t.Errorf("AllocID #0 failed: %v", err)
}
if want := len(keys); want != 5 {
t.Errorf("Expected to allocate 5 keys, %d keys are found", want)
}
for _, k := range keys {
if k.Incomplete() {
t.Errorf("Unexpeceted incomplete key found: %v", k)
}
}
}
func TestGetAllWithFieldMismatch(t *testing.T) {
type Fat struct {
X, Y int
}
type Thin struct {
X int
}
ctx := context.Background()
client := newClient(ctx)
// Ancestor queries (those within an entity group) are strongly consistent
// by default, which prevents a test from being flaky.
// See https://cloud.google.com/appengine/docs/go/datastore/queries#Go_Data_consistency
// for more information.
parent := NewKey(ctx, "SQParent", "TestGetAllWithFieldMismatch", 0, nil)
putKeys := make([]*Key, 3)
for i := range putKeys {
putKeys[i] = NewKey(ctx, "GetAllThing", "", int64(10+i), parent)
_, err := client.Put(ctx, putKeys[i], &Fat{X: 20 + i, Y: 30 + i})
if err != nil {
t.Fatalf("client.Put: %v", err)
}
}
var got []Thin
want := []Thin{
{X: 20},
{X: 21},
{X: 22},
}
getKeys, err := client.GetAll(ctx, NewQuery("GetAllThing").Ancestor(parent), &got)
if len(getKeys) != 3 && !reflect.DeepEqual(getKeys, putKeys) {
t.Errorf("client.GetAll: keys differ\ngetKeys=%v\nputKeys=%v", getKeys, putKeys)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("client.GetAll: entities differ\ngot =%v\nwant=%v", got, want)
}
if _, ok := err.(*ErrFieldMismatch); !ok {
t.Errorf("client.GetAll: got err=%v, want ErrFieldMismatch", err)
}
}
func TestKindlessQueries(t *testing.T) {
type Dee struct {
I int
Why string
}
type Dum struct {
I int
Pling string
}
ctx := context.Background()
client := newClient(ctx)
parent := NewKey(ctx, "Tweedle", "tweedle", 0, nil)
keys := []*Key{
NewKey(ctx, "Dee", "dee0", 0, parent),
NewKey(ctx, "Dum", "dum1", 0, parent),
NewKey(ctx, "Dum", "dum2", 0, parent),
NewKey(ctx, "Dum", "dum3", 0, parent),
}
src := []interface{}{
&Dee{1, "binary0001"},
&Dum{2, "binary0010"},
&Dum{4, "binary0100"},
&Dum{8, "binary1000"},
}
keys, err := client.PutMulti(ctx, keys, src)
if err != nil {
t.Fatalf("put: %v", err)
}
testCases := []struct {
desc string
query *Query
want []int
wantErr string
}{
{
desc: "Dee",
query: NewQuery("Dee"),
want: []int{1},
},
{
desc: "Doh",
query: NewQuery("Doh"),
want: nil},
{
desc: "Dum",
query: NewQuery("Dum"),
want: []int{2, 4, 8},
},
{
desc: "",
query: NewQuery(""),
want: []int{1, 2, 4, 8},
},
{
desc: "Kindless filter",
query: NewQuery("").Filter("__key__ =", keys[2]),
want: []int{4},
},
{
desc: "Kindless order",
query: NewQuery("").Order("__key__"),
want: []int{1, 2, 4, 8},
},
{
desc: "Kindless bad filter",
query: NewQuery("").Filter("I =", 4),
wantErr: "kind is required for filter: I",
},
{
desc: "Kindless bad order",
query: NewQuery("").Order("-__key__"),
wantErr: "kind is required for all orders except __key__ ascending",
},
}
loop:
for _, tc := range testCases {
q := tc.query.Ancestor(parent)
gotCount, err := client.Count(ctx, q)
if err != nil {
if tc.wantErr == "" || !strings.Contains(err.Error(), tc.wantErr) {
t.Errorf("count %q: err %v, want err %q", tc.desc, err, tc.wantErr)
}
continue
}
if tc.wantErr != "" {
t.Errorf("count %q: want err %q", tc.desc, tc.wantErr)
continue
}
if gotCount != len(tc.want) {
t.Errorf("count %q: got %d want %d", tc.desc, gotCount, len(tc.want))
continue
}
var got []int
for iter := client.Run(ctx, q); ; {
var dst struct {
I int
Why, Pling string
}
_, err := iter.Next(&dst)
if err == Done {
break
}
if err != nil {
t.Errorf("iter.Next %q: %v", tc.desc, err)
continue loop
}
got = append(got, dst.I)
}
sort.Ints(got)
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("elems %q: got %+v want %+v", tc.desc, got, tc.want)
continue
}
}
}

277
vendor/google.golang.org/cloud/datastore/key.go generated vendored Normal file
View file

@ -0,0 +1,277 @@
// Copyright 2014 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 (
"bytes"
"encoding/base64"
"encoding/gob"
"errors"
"strconv"
"strings"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
pb "google.golang.org/cloud/internal/datastore"
)
// Key represents the datastore key for a stored entity, and is immutable.
type Key struct {
kind string
id int64
name string
parent *Key
namespace string
}
func (k *Key) Kind() string {
return k.kind
}
func (k *Key) ID() int64 {
return k.id
}
func (k *Key) Name() string {
return k.name
}
func (k *Key) Parent() *Key {
return k.parent
}
func (k *Key) SetParent(v *Key) {
if v.Incomplete() {
panic("can't set an incomplete key as parent")
}
k.parent = v
}
func (k *Key) Namespace() string {
return k.namespace
}
// Complete returns whether the key does not refer to a stored entity.
func (k *Key) Incomplete() bool {
return k.name == "" && k.id == 0
}
// valid returns whether the key is valid.
func (k *Key) valid() bool {
if k == nil {
return false
}
for ; k != nil; k = k.parent {
if k.kind == "" {
return false
}
if k.name != "" && k.id != 0 {
return false
}
if k.parent != nil {
if k.parent.Incomplete() {
return false
}
if k.parent.namespace != k.namespace {
return false
}
}
}
return true
}
func (k *Key) Equal(o *Key) bool {
for {
if k == nil || o == nil {
return k == o // if either is nil, both must be nil
}
if k.namespace != o.namespace || k.name != o.name || k.id != o.id || k.kind != o.kind {
return false
}
if k.parent == nil && o.parent == nil {
return true
}
k = k.parent
o = o.parent
}
}
// marshal marshals the key's string representation to the buffer.
func (k *Key) marshal(b *bytes.Buffer) {
if k.parent != nil {
k.parent.marshal(b)
}
b.WriteByte('/')
b.WriteString(k.kind)
b.WriteByte(',')
if k.name != "" {
b.WriteString(k.name)
} else {
b.WriteString(strconv.FormatInt(k.id, 10))
}
}
// String returns a string representation of the key.
func (k *Key) String() string {
if k == nil {
return ""
}
b := bytes.NewBuffer(make([]byte, 0, 512))
k.marshal(b)
return b.String()
}
// Note: Fields not renamed compared to appengine gobKey struct
// This ensures gobs created by appengine can be read here, and vice/versa
type gobKey struct {
Kind string
StringID string
IntID int64
Parent *gobKey
AppID string
Namespace string
}
func keyToGobKey(k *Key) *gobKey {
if k == nil {
return nil
}
return &gobKey{
Kind: k.kind,
StringID: k.name,
IntID: k.id,
Parent: keyToGobKey(k.parent),
Namespace: k.namespace,
}
}
func gobKeyToKey(gk *gobKey) *Key {
if gk == nil {
return nil
}
return &Key{
kind: gk.Kind,
name: gk.StringID,
id: gk.IntID,
parent: gobKeyToKey(gk.Parent),
namespace: gk.Namespace,
}
}
func (k *Key) GobEncode() ([]byte, error) {
buf := new(bytes.Buffer)
if err := gob.NewEncoder(buf).Encode(keyToGobKey(k)); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (k *Key) GobDecode(buf []byte) error {
gk := new(gobKey)
if err := gob.NewDecoder(bytes.NewBuffer(buf)).Decode(gk); err != nil {
return err
}
*k = *gobKeyToKey(gk)
return nil
}
func (k *Key) MarshalJSON() ([]byte, error) {
return []byte(`"` + k.Encode() + `"`), nil
}
func (k *Key) UnmarshalJSON(buf []byte) error {
if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' {
return errors.New("datastore: bad JSON key")
}
k2, err := DecodeKey(string(buf[1 : len(buf)-1]))
if err != nil {
return err
}
*k = *k2
return nil
}
// Encode returns an opaque representation of the key
// suitable for use in HTML and URLs.
// This is compatible with the Python and Java runtimes.
func (k *Key) Encode() string {
pKey := keyToProto(k)
b, err := proto.Marshal(pKey)
if err != nil {
panic(err)
}
// Trailing padding is stripped.
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
}
// DecodeKey decodes a key from the opaque representation returned by Encode.
func DecodeKey(encoded string) (*Key, error) {
// Re-add padding.
if m := len(encoded) % 4; m != 0 {
encoded += strings.Repeat("=", 4-m)
}
b, err := base64.URLEncoding.DecodeString(encoded)
if err != nil {
return nil, err
}
pKey := new(pb.Key)
if err := proto.Unmarshal(b, pKey); err != nil {
return nil, err
}
return protoToKey(pKey), nil
}
// NewIncompleteKey creates a new incomplete key.
// kind cannot be empty.
func NewIncompleteKey(ctx context.Context, kind string, parent *Key) *Key {
return NewKey(ctx, kind, "", 0, parent)
}
// NewKey creates a new key.
// kind cannot be empty.
// Either one or both of name and id must be zero. If both are zero,
// the key returned is incomplete.
// parent must either be a complete key or nil.
func NewKey(ctx context.Context, kind, name string, id int64, parent *Key) *Key {
return &Key{
kind: kind,
name: name,
id: id,
parent: parent,
namespace: ctxNamespace(ctx),
}
}
// AllocateIDs accepts a slice of incomplete keys and returns a
// slice of complete keys that are guaranteed to be valid in the datastore
func (c *Client) AllocateIDs(ctx context.Context, keys []*Key) ([]*Key, error) {
if keys == nil {
return nil, nil
}
req := &pb.AllocateIdsRequest{Key: multiKeyToProto(keys)}
res := &pb.AllocateIdsResponse{}
if err := c.call(ctx, "allocateIds", req, res); err != nil {
return nil, err
}
return multiProtoToKey(res.Key), nil
}

223
vendor/google.golang.org/cloud/datastore/key_test.go generated vendored Normal file
View file

@ -0,0 +1,223 @@
// Copyright 2014 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 (
"bytes"
"encoding/gob"
"encoding/json"
"testing"
"golang.org/x/net/context"
)
func TestNamespace(t *testing.T) {
c := context.Background()
k := NewIncompleteKey(c, "foo", nil)
if got, want := k.Namespace(), ""; got != want {
t.Errorf("No namespace, k.Namespace() = %q, want %q", got, want)
}
c = WithNamespace(c, "gopherspace")
k = NewIncompleteKey(c, "foo", nil)
if got, want := k.Namespace(), "gopherspace"; got != want {
t.Errorf("No namespace, k.Namespace() = %q, want %q", got, want)
}
}
func TestParent(t *testing.T) {
c := context.Background()
k := NewIncompleteKey(c, "foo", nil)
par := NewKey(c, "foomum", "", 1248, nil)
k.SetParent(par)
if got := k.Parent(); got != par {
t.Errorf("k.Parent() = %v; want %v", got, par)
}
}
func TestEqual(t *testing.T) {
c := context.Background()
cN := WithNamespace(c, "gopherspace")
testCases := []struct {
x, y *Key
equal bool
}{
{
x: nil,
y: nil,
equal: true,
},
{
x: NewKey(c, "kindA", "", 0, nil),
y: NewIncompleteKey(c, "kindA", nil),
equal: true,
},
{
x: NewKey(c, "kindA", "nameA", 0, nil),
y: NewKey(c, "kindA", "nameA", 0, nil),
equal: true,
},
{
x: NewKey(cN, "kindA", "nameA", 0, nil),
y: NewKey(cN, "kindA", "nameA", 0, nil),
equal: true,
},
{
x: NewKey(c, "kindA", "", 1337, NewKey(c, "kindX", "nameX", 0, nil)),
y: NewKey(c, "kindA", "", 1337, NewKey(c, "kindX", "nameX", 0, nil)),
equal: true,
},
{
x: NewKey(c, "kindA", "nameA", 0, nil),
y: NewKey(c, "kindB", "nameA", 0, nil),
equal: false,
},
{
x: NewKey(c, "kindA", "nameA", 0, nil),
y: NewKey(c, "kindA", "nameB", 0, nil),
equal: false,
},
{
x: NewKey(c, "kindA", "nameA", 0, nil),
y: NewKey(c, "kindA", "", 1337, nil),
equal: false,
},
{
x: NewKey(c, "kindA", "nameA", 0, nil),
y: NewKey(cN, "kindA", "nameA", 0, nil),
equal: false,
},
{
x: NewKey(c, "kindA", "", 1337, NewKey(c, "kindX", "nameX", 0, nil)),
y: NewKey(c, "kindA", "", 1337, NewKey(c, "kindY", "nameX", 0, nil)),
equal: false,
},
{
x: NewKey(c, "kindA", "", 1337, NewKey(c, "kindX", "nameX", 0, nil)),
y: NewKey(c, "kindA", "", 1337, nil),
equal: false,
},
}
for _, tt := range testCases {
if got := tt.x.Equal(tt.y); got != tt.equal {
t.Errorf("Equal(%v, %v) = %t; want %t", tt.x, tt.y, got, tt.equal)
}
if got := tt.y.Equal(tt.x); got != tt.equal {
t.Errorf("Equal(%v, %v) = %t; want %t", tt.y, tt.x, got, tt.equal)
}
}
}
func TestEncoding(t *testing.T) {
c := context.Background()
cN := WithNamespace(c, "gopherspace")
testCases := []struct {
k *Key
valid bool
}{
{
k: nil,
valid: false,
},
{
k: NewKey(c, "", "", 0, nil),
valid: false,
},
{
k: NewKey(c, "kindA", "", 0, nil),
valid: true,
},
{
k: NewKey(cN, "kindA", "", 0, nil),
valid: true,
},
{
k: NewKey(c, "kindA", "nameA", 0, nil),
valid: true,
},
{
k: NewKey(c, "kindA", "", 1337, nil),
valid: true,
},
{
k: NewKey(c, "kindA", "nameA", 1337, nil),
valid: false,
},
{
k: NewKey(c, "kindA", "", 0, NewKey(c, "kindB", "nameB", 0, nil)),
valid: true,
},
{
k: NewKey(c, "kindA", "", 0, NewKey(c, "kindB", "", 0, nil)),
valid: false,
},
{
k: NewKey(c, "kindA", "", 0, NewKey(cN, "kindB", "nameB", 0, nil)),
valid: false,
},
}
for _, tt := range testCases {
if got := tt.k.valid(); got != tt.valid {
t.Errorf("valid(%v) = %t; want %t", tt.k, got, tt.valid)
}
// Check encoding/decoding for valid keys.
if !tt.valid {
continue
}
enc := tt.k.Encode()
dec, err := DecodeKey(enc)
if err != nil {
t.Errorf("DecodeKey(%q) from %v: %v", enc, tt.k, err)
continue
}
if !tt.k.Equal(dec) {
t.Errorf("Decoded key %v not equal to %v", dec, tt.k)
}
b, err := json.Marshal(tt.k)
if err != nil {
t.Errorf("json.Marshal(%v): %v", tt.k, err)
continue
}
key := &Key{}
if err := json.Unmarshal(b, key); err != nil {
t.Errorf("json.Unmarshal(%s) for key %v: %v", b, tt.k, err)
continue
}
if !tt.k.Equal(key) {
t.Errorf("JSON decoded key %v not equal to %v", dec, tt.k)
}
buf := &bytes.Buffer{}
gobEnc := gob.NewEncoder(buf)
if err := gobEnc.Encode(tt.k); err != nil {
t.Errorf("gobEnc.Encode(%v): %v", tt.k, err)
continue
}
gobDec := gob.NewDecoder(buf)
key = &Key{}
if err := gobDec.Decode(key); err != nil {
t.Errorf("gobDec.Decode() for key %v: %v", tt.k, err)
}
if !tt.k.Equal(key) {
t.Errorf("gob decoded key %v not equal to %v", dec, tt.k)
}
}
}

275
vendor/google.golang.org/cloud/datastore/load.go generated vendored Normal file
View file

@ -0,0 +1,275 @@
// Copyright 2014 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"
"reflect"
"time"
pb "google.golang.org/cloud/internal/datastore"
)
var (
typeOfByteSlice = reflect.TypeOf([]byte(nil))
typeOfTime = reflect.TypeOf(time.Time{})
)
// typeMismatchReason returns a string explaining why the property p could not
// be stored in an entity field of type v.Type().
func typeMismatchReason(p Property, v reflect.Value) string {
entityType := "empty"
switch p.Value.(type) {
case int64:
entityType = "int"
case bool:
entityType = "bool"
case string:
entityType = "string"
case float64:
entityType = "float"
case *Key:
entityType = "*datastore.Key"
case time.Time:
entityType = "time.Time"
case []byte:
entityType = "[]byte"
}
return fmt.Sprintf("type mismatch: %s versus %v", entityType, 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.
m map[string]int
}
func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p Property, prev map[string]struct{}) string {
var sliceOk bool
var v reflect.Value
// Traverse a struct's struct-typed fields.
for name := p.Name; ; {
decoder, ok := codec.byName[name]
if !ok {
return "no such struct field"
}
v = structValue.Field(decoder.index)
if !v.IsValid() {
return "no such struct field"
}
if !v.CanSet() {
return "cannot set struct field"
}
if decoder.substructCodec == nil {
break
}
if v.Kind() == reflect.Slice {
if l.m == nil {
l.m = make(map[string]int)
}
index := l.m[p.Name]
l.m[p.Name] = index + 1
for v.Len() <= index {
v.Set(reflect.Append(v, reflect.New(v.Type().Elem()).Elem()))
}
structValue = v.Index(index)
sliceOk = true
} else {
structValue = v
}
// Strip the "I." from "I.X".
name = name[len(codec.byIndex[decoder.index].name):]
codec = decoder.substructCodec
}
var slice reflect.Value
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
slice = v
v = reflect.New(v.Type().Elem()).Elem()
} else if _, ok := prev[p.Name]; ok && !sliceOk {
// Zero the field back out that was set previously, turns out its a slice and we don't know what to do with it
v.Set(reflect.Zero(v.Type()))
return "multiple-valued property requires a slice field type"
}
prev[p.Name] = struct{}{}
pValue := p.Value
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x, ok := pValue.(int64)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
if v.OverflowInt(x) {
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
}
v.SetInt(x)
case reflect.Bool:
x, ok := pValue.(bool)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
v.SetBool(x)
case reflect.String:
x, ok := pValue.(string)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
v.SetString(x)
case reflect.Float32, reflect.Float64:
x, ok := pValue.(float64)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
if v.OverflowFloat(x) {
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
}
v.SetFloat(x)
case reflect.Ptr:
x, ok := pValue.(*Key)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
if _, ok := v.Interface().(*Key); !ok {
return typeMismatchReason(p, v)
}
v.Set(reflect.ValueOf(x))
case reflect.Struct:
switch v.Type() {
case typeOfTime:
x, ok := pValue.(time.Time)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
v.Set(reflect.ValueOf(x))
default:
return typeMismatchReason(p, v)
}
case reflect.Slice:
x, ok := pValue.([]byte)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
if v.Type().Elem().Kind() != reflect.Uint8 {
return typeMismatchReason(p, v)
}
v.SetBytes(x)
default:
return typeMismatchReason(p, v)
}
if slice.IsValid() {
slice.Set(reflect.Append(slice, v))
}
return ""
}
// loadEntity loads an EntityProto into PropertyLoadSaver or struct pointer.
func loadEntity(dst interface{}, src *pb.Entity) (err error) {
props := protoToProperties(src)
if e, ok := dst.(PropertyLoadSaver); ok {
return e.Load(props)
}
return LoadStruct(dst, props)
}
func (s structPLS) Load(props []Property) error {
var fieldName, reason string
var l propertyLoader
prev := make(map[string]struct{})
for _, p := range props {
if errStr := l.load(s.codec, s.v, p, prev); errStr != "" {
// We don't return early, as we try to load as many properties as possible.
// It is valid to load an entity into a struct that cannot fully represent it.
// That case returns an error, but the caller is free to ignore it.
fieldName, reason = p.Name, errStr
}
}
if reason != "" {
return &ErrFieldMismatch{
StructType: s.v.Type(),
FieldName: fieldName,
Reason: reason,
}
}
return nil
}
func protoToProperties(src *pb.Entity) []Property {
props := src.Property
out := make([]Property, 0, len(props))
for {
var (
x *pb.Property
noIndex bool
)
if len(props) > 0 {
x, props = props[0], props[1:]
noIndex = !x.GetValue().GetIndexed()
} else {
break
}
if x.Value.ListValue == nil {
out = append(out, Property{
Name: x.GetName(),
Value: propValue(x.Value),
NoIndex: noIndex,
Multiple: false,
})
} else {
for _, v := range x.Value.ListValue {
out = append(out, Property{
Name: x.GetName(),
Value: propValue(v),
NoIndex: noIndex,
Multiple: true,
})
}
}
}
return out
}
// propValue returns a Go value that combines the raw PropertyValue with a
// meaning. For example, an Int64Value with GD_WHEN becomes a time.Time.
func propValue(v *pb.Value) interface{} {
//TODO(PSG-Luna): Support EntityValue
//TODO(PSG-Luna): GeoPoint seems gone from the v1 proto, reimplement it once it's readded
switch {
case v.IntegerValue != nil:
return *v.IntegerValue
case v.TimestampMicrosecondsValue != nil:
return fromUnixMicro(*v.TimestampMicrosecondsValue)
case v.BooleanValue != nil:
return *v.BooleanValue
case v.StringValue != nil:
return *v.StringValue
case v.BlobValue != nil:
return []byte(v.BlobValue)
case v.BlobKeyValue != nil:
return *v.BlobKeyValue
case v.DoubleValue != nil:
return *v.DoubleValue
case v.KeyValue != nil:
return protoToKey(v.KeyValue)
}
return nil
}

300
vendor/google.golang.org/cloud/datastore/prop.go generated vendored Normal file
View file

@ -0,0 +1,300 @@
// Copyright 2014 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"
"reflect"
"strings"
"sync"
"unicode"
)
// Entities with more than this many indexed properties will not be saved.
const maxIndexedProperties = 5000
// []byte fields more than 1 megabyte long will not be loaded or saved.
const maxBlobLen = 1 << 20
// Property is a name/value pair plus some metadata. A datastore entity's
// contents are loaded and saved as a sequence of Properties. An entity can
// have multiple Properties with the same name, provided that p.Multiple is
// true on all of that entity's Properties with that name.
type Property struct {
// Name is the property name.
Name string
// Value is the property value. The valid types are:
// - int64
// - bool
// - string
// - float64
// - *Key
// - time.Time
// - []byte (up to 1 megabyte in length)
// This set is smaller than the set of valid struct field types that the
// datastore can load and save. A Property Value cannot be a slice (apart
// from []byte); use multiple Properties instead. Also, a Value's type
// must be explicitly on the list above; it is not sufficient for the
// underlying type to be on that list. For example, a Value of "type
// myInt64 int64" is invalid. Smaller-width integers and floats are also
// invalid. Again, this is more restrictive than the set of valid struct
// field types.
//
// A Value will have an opaque type when loading entities from an index,
// such as via a projection query. Load entities into a struct instead
// of a PropertyLoadSaver when using a projection query.
//
// A Value may also be the nil interface value; this is equivalent to
// Python's None but not directly representable by a Go struct. Loading
// a nil-valued property into a struct will set that field to the zero
// value.
Value interface{}
// NoIndex is whether the datastore cannot index this property.
// If NoIndex is set to false, []byte values are limited to 1500 bytes and
// string values are limited to 1500 bytes.
NoIndex bool
// Multiple is whether the entity can have multiple properties with
// the same name. Even if a particular instance only has one property with
// a certain name, Multiple should be true if a struct would best represent
// it as a field of type []T instead of type T.
Multiple bool
}
// PropertyLoadSaver can be converted from and to a slice of Properties.
type PropertyLoadSaver interface {
Load([]Property) error
Save() ([]Property, error)
}
// PropertyList converts a []Property to implement PropertyLoadSaver.
type PropertyList []Property
var (
typeOfPropertyLoadSaver = reflect.TypeOf((*PropertyLoadSaver)(nil)).Elem()
typeOfPropertyList = reflect.TypeOf(PropertyList(nil))
)
// Load loads all of the provided properties into l.
// It does not first reset *l to an empty slice.
func (l *PropertyList) Load(p []Property) error {
*l = append(*l, p...)
return nil
}
// Save saves all of l's properties as a slice of Properties.
func (l *PropertyList) Save() ([]Property, error) {
return *l, nil
}
// validPropertyName returns whether name consists of one or more valid Go
// identifiers joined by ".".
func validPropertyName(name string) bool {
if name == "" {
return false
}
for _, s := range strings.Split(name, ".") {
if s == "" {
return false
}
first := true
for _, c := range s {
if first {
first = false
if c != '_' && !unicode.IsLetter(c) {
return false
}
} else {
if c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
}
}
return true
}
// structTag is the parsed `datastore:"name,options"` tag of a struct field.
// If a field has no tag, or the tag has an empty name, then the structTag's
// name is just the field name. A "-" name means that the datastore ignores
// that field.
type structTag struct {
name string
noIndex bool
}
// structCodec describes how to convert a struct to and from a sequence of
// properties.
type structCodec struct {
// byIndex gives the structTag for the i'th field.
byIndex []structTag
// byName gives the field codec for the structTag with the given name.
byName map[string]fieldCodec
// hasSlice is whether a struct or any of its nested or embedded structs
// has a slice-typed field (other than []byte).
hasSlice bool
// complete is whether the structCodec is complete. An incomplete
// structCodec may be encountered when walking a recursive struct.
complete bool
}
// fieldCodec is a struct field's index and, if that struct field's type is
// itself a struct, that substruct's structCodec.
type fieldCodec struct {
index int
substructCodec *structCodec
}
// structCodecs collects the structCodecs that have already been calculated.
var (
structCodecsMutex sync.Mutex
structCodecs = make(map[reflect.Type]*structCodec)
)
// getStructCodec returns the structCodec for the given struct type.
func getStructCodec(t reflect.Type) (*structCodec, error) {
structCodecsMutex.Lock()
defer structCodecsMutex.Unlock()
return getStructCodecLocked(t)
}
// getStructCodecLocked implements getStructCodec. The structCodecsMutex must
// be held when calling this function.
func getStructCodecLocked(t reflect.Type) (ret *structCodec, retErr error) {
c, ok := structCodecs[t]
if ok {
return c, nil
}
c = &structCodec{
byIndex: make([]structTag, t.NumField()),
byName: make(map[string]fieldCodec),
}
// Add c to the structCodecs map before we are sure it is good. If t is
// a recursive type, it needs to find the incomplete entry for itself in
// the map.
structCodecs[t] = c
defer func() {
if retErr != nil {
delete(structCodecs, t)
}
}()
for i := range c.byIndex {
f := t.Field(i)
name, opts := f.Tag.Get("datastore"), ""
if i := strings.Index(name, ","); i != -1 {
name, opts = name[:i], name[i+1:]
}
if name == "" {
if !f.Anonymous {
name = f.Name
}
} else if name == "-" {
c.byIndex[i] = structTag{name: name}
continue
} else if !validPropertyName(name) {
return nil, fmt.Errorf("datastore: struct tag has invalid property name: %q", name)
}
substructType, fIsSlice := reflect.Type(nil), false
switch f.Type.Kind() {
case reflect.Struct:
substructType = f.Type
case reflect.Slice:
if f.Type.Elem().Kind() == reflect.Struct {
substructType = f.Type.Elem()
}
fIsSlice = f.Type != typeOfByteSlice
c.hasSlice = c.hasSlice || fIsSlice
}
if substructType != nil && substructType != typeOfTime {
if name != "" {
name = name + "."
}
sub, err := getStructCodecLocked(substructType)
if err != nil {
return nil, err
}
if !sub.complete {
return nil, fmt.Errorf("datastore: recursive struct: field %q", f.Name)
}
if fIsSlice && sub.hasSlice {
return nil, fmt.Errorf(
"datastore: flattening nested structs leads to a slice of slices: field %q", f.Name)
}
c.hasSlice = c.hasSlice || sub.hasSlice
for relName := range sub.byName {
absName := name + relName
if _, ok := c.byName[absName]; ok {
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", absName)
}
c.byName[absName] = fieldCodec{index: i, substructCodec: sub}
}
} else {
if _, ok := c.byName[name]; ok {
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", name)
}
c.byName[name] = fieldCodec{index: i}
}
c.byIndex[i] = structTag{
name: name,
noIndex: opts == "noindex",
}
}
c.complete = true
return c, nil
}
// structPLS adapts a struct to be a PropertyLoadSaver.
type structPLS struct {
v reflect.Value
codec *structCodec
}
// newStructPLS returns a PropertyLoadSaver for the struct pointer p.
func newStructPLS(p interface{}) (PropertyLoadSaver, error) {
v := reflect.ValueOf(p)
if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
return nil, ErrInvalidEntityType
}
v = v.Elem()
codec, err := getStructCodec(v.Type())
if err != nil {
return nil, err
}
return structPLS{v, codec}, nil
}
// LoadStruct loads the properties from p to dst.
// dst must be a struct pointer.
func LoadStruct(dst interface{}, p []Property) error {
x, err := newStructPLS(dst)
if err != nil {
return err
}
return x.Load(p)
}
// SaveStruct returns the properties from src as a slice of Properties.
// src must be a struct pointer.
func SaveStruct(src interface{}) ([]Property, error) {
x, err := newStructPLS(src)
if err != nil {
return nil, err
}
return x.Save()
}

755
vendor/google.golang.org/cloud/datastore/query.go generated vendored Normal file
View file

@ -0,0 +1,755 @@
// Copyright 2014 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 (
"encoding/base64"
"errors"
"fmt"
"math"
"reflect"
"strconv"
"strings"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
pb "google.golang.org/cloud/internal/datastore"
)
type operator int
const (
lessThan operator = iota
lessEq
equal
greaterEq
greaterThan
keyFieldName = "__key__"
)
var operatorToProto = map[operator]*pb.PropertyFilter_Operator{
lessThan: pb.PropertyFilter_LESS_THAN.Enum(),
lessEq: pb.PropertyFilter_LESS_THAN_OR_EQUAL.Enum(),
equal: pb.PropertyFilter_EQUAL.Enum(),
greaterEq: pb.PropertyFilter_GREATER_THAN_OR_EQUAL.Enum(),
greaterThan: pb.PropertyFilter_GREATER_THAN.Enum(),
}
// filter is a conditional filter on query results.
type filter struct {
FieldName string
Op operator
Value interface{}
}
type sortDirection int
const (
ascending sortDirection = iota
descending
)
var sortDirectionToProto = map[sortDirection]*pb.PropertyOrder_Direction{
ascending: pb.PropertyOrder_ASCENDING.Enum(),
descending: pb.PropertyOrder_DESCENDING.Enum(),
}
// order is a sort order on query results.
type order struct {
FieldName string
Direction sortDirection
}
// NewQuery creates a new Query for a specific entity kind.
//
// An empty kind means to return all entities, including entities created and
// managed by other App Engine features, and is called a kindless query.
// Kindless queries cannot include filters or sort orders on property values.
func NewQuery(kind string) *Query {
return &Query{
kind: kind,
limit: -1,
}
}
// Query represents a datastore query.
type Query struct {
kind string
ancestor *Key
filter []filter
order []order
projection []string
distinct bool
keysOnly bool
eventual bool
limit int32
offset int32
start []byte
end []byte
trans *Transaction
err error
}
func (q *Query) clone() *Query {
x := *q
// Copy the contents of the slice-typed fields to a new backing store.
if len(q.filter) > 0 {
x.filter = make([]filter, len(q.filter))
copy(x.filter, q.filter)
}
if len(q.order) > 0 {
x.order = make([]order, len(q.order))
copy(x.order, q.order)
}
return &x
}
// Ancestor returns a derivative query with an ancestor filter.
// The ancestor should not be nil.
func (q *Query) Ancestor(ancestor *Key) *Query {
q = q.clone()
if ancestor == nil {
q.err = errors.New("datastore: nil query ancestor")
return q
}
q.ancestor = ancestor
return q
}
// EventualConsistency returns a derivative query that returns eventually
// consistent results.
// It only has an effect on ancestor queries.
func (q *Query) EventualConsistency() *Query {
q = q.clone()
q.eventual = true
return q
}
// Transaction returns a derivative query that is associated with the given
// transaction.
//
// All reads performed as part of the transaction will come from a single
// consistent 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 (q *Query) Transaction(t *Transaction) *Query {
q = q.clone()
q.trans = t
return q
}
// Filter returns a derivative query with a field-based filter.
// The filterStr argument must be a field name followed by optional space,
// followed by an operator, one of ">", "<", ">=", "<=", or "=".
// Fields are compared against the provided value using the operator.
// Multiple filters are AND'ed together.
// Field names which contain spaces, quote marks, or operator characters
// should be passed as quoted Go string literals as returned by strconv.Quote
// or the fmt package's %q verb.
func (q *Query) Filter(filterStr string, value interface{}) *Query {
q = q.clone()
filterStr = strings.TrimSpace(filterStr)
if filterStr == "" {
q.err = fmt.Errorf("datastore: invalid filter %q", filterStr)
return q
}
f := filter{
FieldName: strings.TrimRight(filterStr, " ><=!"),
Value: value,
}
switch op := strings.TrimSpace(filterStr[len(f.FieldName):]); op {
case "<=":
f.Op = lessEq
case ">=":
f.Op = greaterEq
case "<":
f.Op = lessThan
case ">":
f.Op = greaterThan
case "=":
f.Op = equal
default:
q.err = fmt.Errorf("datastore: invalid operator %q in filter %q", op, filterStr)
return q
}
var err error
f.FieldName, err = unquote(f.FieldName)
if err != nil {
q.err = fmt.Errorf("datastore: invalid syntax for quoted field name %q", f.FieldName)
return q
}
q.filter = append(q.filter, f)
return q
}
// Order returns a derivative query with a field-based sort order. Orders are
// applied in the order they are added. The default order is ascending; to sort
// in descending order prefix the fieldName with a minus sign (-).
// Field names which contain spaces, quote marks, or the minus sign
// should be passed as quoted Go string literals as returned by strconv.Quote
// or the fmt package's %q verb.
func (q *Query) Order(fieldName string) *Query {
q = q.clone()
fieldName, dir := strings.TrimSpace(fieldName), ascending
if strings.HasPrefix(fieldName, "-") {
fieldName, dir = strings.TrimSpace(fieldName[1:]), descending
} else if strings.HasPrefix(fieldName, "+") {
q.err = fmt.Errorf("datastore: invalid order: %q", fieldName)
return q
}
fieldName, err := unquote(fieldName)
if err != nil {
q.err = fmt.Errorf("datastore: invalid syntax for quoted field name %q", fieldName)
return q
}
if fieldName == "" {
q.err = errors.New("datastore: empty order")
return q
}
q.order = append(q.order, order{
Direction: dir,
FieldName: fieldName,
})
return q
}
// unquote optionally interprets s as a double-quoted or backquoted Go
// string literal if it begins with the relevant character.
func unquote(s string) (string, error) {
if s == "" || (s[0] != '`' && s[0] != '"') {
return s, nil
}
return strconv.Unquote(s)
}
// Project returns a derivative query that yields only the given fields. It
// cannot be used with KeysOnly.
func (q *Query) Project(fieldNames ...string) *Query {
q = q.clone()
q.projection = append([]string(nil), fieldNames...)
return q
}
// Distinct returns a derivative query that yields de-duplicated entities with
// respect to the set of projected fields. It is only used for projection
// queries.
func (q *Query) Distinct() *Query {
q = q.clone()
q.distinct = true
return q
}
// KeysOnly returns a derivative query that yields only keys, not keys and
// entities. It cannot be used with projection queries.
func (q *Query) KeysOnly() *Query {
q = q.clone()
q.keysOnly = true
return q
}
// Limit returns a derivative query that has a limit on the number of results
// returned. A negative value means unlimited.
func (q *Query) Limit(limit int) *Query {
q = q.clone()
if limit < math.MinInt32 || limit > math.MaxInt32 {
q.err = errors.New("datastore: query limit overflow")
return q
}
q.limit = int32(limit)
return q
}
// Offset returns a derivative query that has an offset of how many keys to
// skip over before returning results. A negative value is invalid.
func (q *Query) Offset(offset int) *Query {
q = q.clone()
if offset < 0 {
q.err = errors.New("datastore: negative query offset")
return q
}
if offset > math.MaxInt32 {
q.err = errors.New("datastore: query offset overflow")
return q
}
q.offset = int32(offset)
return q
}
// Start returns a derivative query with the given start point.
func (q *Query) Start(c Cursor) *Query {
q = q.clone()
if c.cc == nil {
q.err = errors.New("datastore: invalid cursor")
return q
}
q.start = c.cc
return q
}
// End returns a derivative query with the given end point.
func (q *Query) End(c Cursor) *Query {
q = q.clone()
if c.cc == nil {
q.err = errors.New("datastore: invalid cursor")
return q
}
q.end = c.cc
return q
}
// toProto converts the query to a protocol buffer.
func (q *Query) toProto(req *pb.RunQueryRequest) error {
dst := pb.Query{}
if len(q.projection) != 0 && q.keysOnly {
return errors.New("datastore: query cannot both project and be keys-only")
}
dst.Reset()
if q.kind != "" {
dst.Kind = []*pb.KindExpression{&pb.KindExpression{Name: proto.String(q.kind)}}
}
if q.projection != nil {
for _, propertyName := range q.projection {
dst.Projection = append(dst.Projection, &pb.PropertyExpression{Property: &pb.PropertyReference{Name: proto.String(propertyName)}})
}
if q.distinct {
for _, propertyName := range q.projection {
dst.GroupBy = append(dst.GroupBy, &pb.PropertyReference{Name: proto.String(propertyName)})
}
}
}
if q.keysOnly {
dst.Projection = []*pb.PropertyExpression{&pb.PropertyExpression{Property: &pb.PropertyReference{Name: proto.String(keyFieldName)}}}
}
var filters []*pb.Filter
for _, qf := range q.filter {
if qf.FieldName == "" {
return errors.New("datastore: empty query filter field name")
}
v, errStr := interfaceToProto(reflect.ValueOf(qf.Value).Interface())
if errStr != "" {
return errors.New("datastore: bad query filter value type: " + errStr)
}
xf := &pb.PropertyFilter{
Operator: operatorToProto[qf.Op],
Property: &pb.PropertyReference{Name: proto.String(qf.FieldName)},
Value: v,
}
if xf.Operator == nil {
return errors.New("datastore: unknown query filter operator")
}
filters = append(filters, &pb.Filter{PropertyFilter: xf})
}
if q.ancestor != nil {
filters = append(filters, &pb.Filter{
PropertyFilter: &pb.PropertyFilter{
Property: &pb.PropertyReference{Name: proto.String("__key__")},
Operator: pb.PropertyFilter_HAS_ANCESTOR.Enum(),
Value: &pb.Value{KeyValue: keyToProto(q.ancestor)},
}})
}
if len(filters) == 1 {
dst.Filter = filters[0]
} else if len(filters) > 1 {
dst.Filter = &pb.Filter{CompositeFilter: &pb.CompositeFilter{
Operator: pb.CompositeFilter_AND.Enum(),
Filter: filters,
}}
}
for _, qo := range q.order {
if qo.FieldName == "" {
return errors.New("datastore: empty query order field name")
}
xo := &pb.PropertyOrder{
Property: &pb.PropertyReference{Name: proto.String(qo.FieldName)},
Direction: sortDirectionToProto[qo.Direction],
}
if xo.Direction == nil {
return errors.New("datastore: unknown query order direction")
}
dst.Order = append(dst.Order, xo)
}
if q.limit >= 0 {
dst.Limit = proto.Int32(q.limit)
}
if q.offset != 0 {
dst.Offset = proto.Int32(q.offset)
}
dst.StartCursor = q.start
dst.EndCursor = q.end
if t := q.trans; t != nil {
if t.id == nil {
return errExpiredTransaction
}
req.ReadOptions = &pb.ReadOptions{Transaction: t.id}
}
req.Query = &dst
return nil
}
// Count returns the number of results for the given query.
func (c *Client) Count(ctx context.Context, q *Query) (int, error) {
// Check that the query is well-formed.
if q.err != nil {
return 0, q.err
}
// Run a copy of the query, with keysOnly true (if we're not a projection,
// since the two are incompatible).
newQ := q.clone()
newQ.keysOnly = len(newQ.projection) == 0
req := &pb.RunQueryRequest{}
if ns := ctxNamespace(ctx); ns != "" {
req.PartitionId = &pb.PartitionId{
Namespace: proto.String(ns),
}
}
if err := newQ.toProto(req); err != nil {
return 0, err
}
res := &pb.RunQueryResponse{}
if err := c.call(ctx, "runQuery", req, res); err != nil {
return 0, err
}
var n int
b := res.Batch
for {
n += len(b.GetEntityResult())
if b.GetMoreResults() != pb.QueryResultBatch_NOT_FINISHED {
break
}
var err error
// TODO(jbd): Support count queries that have a limit and an offset.
if err = callNext(ctx, c, req, res, 0, 0); err != nil {
return 0, err
}
}
return int(n), nil
}
func callNext(ctx context.Context, client *Client, req *pb.RunQueryRequest, res *pb.RunQueryResponse, offset, limit int32) error {
if res.GetBatch().EndCursor == nil {
return errors.New("datastore: internal error: server did not return a cursor")
}
req.Query.StartCursor = res.GetBatch().GetEndCursor()
if limit >= 0 {
req.Query.Limit = proto.Int32(limit)
}
if offset != 0 {
req.Query.Offset = proto.Int32(offset)
}
res.Reset()
return client.call(ctx, "runQuery", req, res)
}
// GetAll runs the provided query in the given context and returns all keys
// that match that query, as well as appending the values to dst.
//
// dst must have type *[]S or *[]*S or *[]P, for some struct type S or some non-
// interface, non-pointer type P such that P or *P implements PropertyLoadSaver.
//
// 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.
//
// The keys returned by GetAll will be in a 1-1 correspondence with the entities
// added to dst.
//
// If q is a ``keys-only'' query, GetAll ignores dst and only returns the keys.
func (c *Client) GetAll(ctx context.Context, q *Query, dst interface{}) ([]*Key, error) {
var (
dv reflect.Value
mat multiArgType
elemType reflect.Type
errFieldMismatch error
)
if !q.keysOnly {
dv = reflect.ValueOf(dst)
if dv.Kind() != reflect.Ptr || dv.IsNil() {
return nil, ErrInvalidEntityType
}
dv = dv.Elem()
mat, elemType = checkMultiArg(dv)
if mat == multiArgTypeInvalid || mat == multiArgTypeInterface {
return nil, ErrInvalidEntityType
}
}
var keys []*Key
for t := c.Run(ctx, q); ; {
k, e, err := t.next()
if err == Done {
break
}
if err != nil {
return keys, err
}
if !q.keysOnly {
ev := reflect.New(elemType)
if elemType.Kind() == reflect.Map {
// This is a special case. The zero values of a map type are
// not immediately useful; they have to be make'd.
//
// Funcs and channels are similar, in that a zero value is not useful,
// but even a freshly make'd channel isn't useful: there's no fixed
// channel buffer size that is always going to be large enough, and
// there's no goroutine to drain the other end. Theoretically, these
// types could be supported, for example by sniffing for a constructor
// method or requiring prior registration, but for now it's not a
// frequent enough concern to be worth it. Programmers can work around
// it by explicitly using Iterator.Next instead of the Query.GetAll
// convenience method.
x := reflect.MakeMap(elemType)
ev.Elem().Set(x)
}
if err = loadEntity(ev.Interface(), e); err != nil {
if _, ok := err.(*ErrFieldMismatch); ok {
// We continue loading entities even in the face of field mismatch errors.
// If we encounter any other error, that other error is returned. Otherwise,
// an ErrFieldMismatch is returned.
errFieldMismatch = err
} else {
return keys, err
}
}
if mat != multiArgTypeStructPtr {
ev = ev.Elem()
}
dv.Set(reflect.Append(dv, ev))
}
keys = append(keys, k)
}
return keys, errFieldMismatch
}
// Run runs the given query in the given context.
func (c *Client) Run(ctx context.Context, q *Query) *Iterator {
if q.err != nil {
return &Iterator{err: q.err}
}
t := &Iterator{
ctx: ctx,
client: c,
limit: q.limit,
q: q,
prevCC: q.start,
}
t.req.Reset()
if ns := ctxNamespace(ctx); ns != "" {
t.req.PartitionId = &pb.PartitionId{
Namespace: proto.String(ns),
}
}
if err := q.toProto(&t.req); err != nil {
t.err = err
return t
}
if err := c.call(ctx, "runQuery", &t.req, &t.res); err != nil {
t.err = err
return t
}
b := t.res.GetBatch()
offset := q.offset - b.GetSkippedResults()
for offset > 0 && b.GetMoreResults() == pb.QueryResultBatch_NOT_FINISHED {
t.prevCC = b.GetEndCursor()
var err error
if err = callNext(t.ctx, c, &t.req, &t.res, offset, t.limit); err != nil {
t.err = err
break
}
skip := b.GetSkippedResults()
if skip < 0 {
t.err = errors.New("datastore: internal error: negative number of skipped_results")
break
}
offset -= skip
}
if offset < 0 {
t.err = errors.New("datastore: internal error: query offset was overshot")
}
return t
}
// Iterator is the result of running a query.
type Iterator struct {
ctx context.Context
client *Client
err error
// req is the request we sent previously, we need to keep track of it to resend it
req pb.RunQueryRequest
// res is the result of the most recent RunQuery or Next API call.
res pb.RunQueryResponse
// i is how many elements of res.Result we have iterated over.
i int
// limit is the limit on the number of results this iterator should return.
// A negative value means unlimited.
limit int32
// q is the original query which yielded this iterator.
q *Query
// prevCC is the compiled cursor that marks the end of the previous batch
// of results.
prevCC []byte
}
// Done is returned when a query iteration has completed.
var Done = errors.New("datastore: query has no more results")
// Next returns the key of the next result. When there are no more results,
// Done is returned as the error.
//
// 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) {
k, e, err := t.next()
if err != nil {
return nil, err
}
if dst != nil && !t.q.keysOnly {
err = loadEntity(dst, e)
}
return k, err
}
func (t *Iterator) next() (*Key, *pb.Entity, error) {
if t.err != nil {
return nil, nil, t.err
}
// Issue datastore_v3/Next RPCs as necessary.
b := t.res.GetBatch()
for t.i == len(b.EntityResult) {
if b.GetMoreResults() != pb.QueryResultBatch_NOT_FINISHED {
t.err = Done
return nil, nil, t.err
}
t.prevCC = b.GetEndCursor()
if err := callNext(t.ctx, t.client, &t.req, &t.res, 0, t.limit); err != nil {
t.err = err
return nil, nil, t.err
}
if b.GetSkippedResults() != 0 {
t.err = errors.New("datastore: internal error: iterator has skipped results")
return nil, nil, t.err
}
t.i = 0
if t.limit >= 0 {
t.limit -= int32(len(b.EntityResult))
if t.limit < 0 {
t.err = errors.New("datastore: internal error: query returned more results than the limit")
return nil, nil, t.err
}
}
}
// Extract the key from the t.i'th element of t.res.Result.
e := b.EntityResult[t.i]
t.i++
if e.Entity.Key == nil {
return nil, nil, errors.New("datastore: internal error: server did not return a key")
}
k := protoToKey(e.Entity.Key)
if k.Incomplete() {
return nil, nil, errors.New("datastore: internal error: server returned an invalid key")
}
return k, e.Entity, nil
}
// Cursor returns a cursor for the iterator's current location.
func (t *Iterator) Cursor() (Cursor, error) {
if t.err != nil && t.err != Done {
return Cursor{}, t.err
}
// If we are at either end of the current batch of results,
// return the compiled cursor at that end.
b := t.res.Batch
skipped := b.GetSkippedResults()
if t.i == 0 && skipped == 0 {
if t.prevCC == nil {
// A nil pointer (of type *pb.CompiledCursor) means no constraint:
// passing it as the end cursor of a new query means unlimited results
// (glossing over the integer limit parameter for now).
// A non-nil pointer to an empty pb.CompiledCursor means the start:
// passing it as the end cursor of a new query means 0 results.
// If prevCC was nil, then the original query had no start cursor, but
// Iterator.Cursor should return "the start" instead of unlimited.
return Cursor{}, nil
}
return Cursor{t.prevCC}, nil
}
if t.i == len(b.EntityResult) {
return Cursor{b.EndCursor}, nil
}
// Otherwise, re-run the query offset to this iterator's position, starting from
// the most recent compiled cursor. This is done on a best-effort basis, as it
// is racy; if a concurrent process has added or removed entities, then the
// cursor returned may be inconsistent.
q := t.q.clone()
q.start = t.prevCC
q.offset = skipped + int32(t.i)
q.limit = 0
q.keysOnly = len(q.projection) == 0
t1 := t.client.Run(t.ctx, t.q)
_, _, err := t1.next()
if err != Done {
if err == nil {
err = fmt.Errorf("datastore: internal error: zero-limit query did not have zero results")
}
return Cursor{}, err
}
return Cursor{t1.res.Batch.EndCursor}, nil
}
// Cursor is an iterator's position. It can be converted to and from an opaque
// string. A cursor can be used from different HTTP requests, but only with a
// query with the same kind, ancestor, filter and order constraints.
type Cursor struct {
cc []byte
}
// String returns a base-64 string representation of a cursor.
func (c Cursor) String() string {
if c.cc == nil {
return ""
}
return strings.TrimRight(base64.URLEncoding.EncodeToString(c.cc), "=")
}
// Decode decodes a cursor from its base-64 string representation.
func DecodeCursor(s string) (Cursor, error) {
if s == "" {
return Cursor{}, nil
}
if n := len(s) % 4; n != 0 {
s += strings.Repeat("=", 4-n)
}
b, err := base64.URLEncoding.DecodeString(s)
if err != nil {
return Cursor{}, err
}
return Cursor{b}, nil
}

482
vendor/google.golang.org/cloud/datastore/query_test.go generated vendored Normal file
View file

@ -0,0 +1,482 @@
// Copyright 2014 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 (
"errors"
"fmt"
"reflect"
"testing"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
pb "google.golang.org/cloud/internal/datastore"
)
var (
key1 = &pb.Key{
PathElement: []*pb.Key_PathElement{
{
Kind: proto.String("Gopher"),
Id: proto.Int64(6),
},
},
}
key2 = &pb.Key{
PathElement: []*pb.Key_PathElement{
{
Kind: proto.String("Gopher"),
Id: proto.Int64(6),
},
{
Kind: proto.String("Gopher"),
Id: proto.Int64(8),
},
},
}
)
type fakeClient func(req, resp proto.Message) (err error)
func (c fakeClient) Call(ctx context.Context, method string, req, resp proto.Message) error {
return c(req, resp)
}
func fakeRunQuery(in *pb.RunQueryRequest, out *pb.RunQueryResponse) error {
expectedIn := &pb.RunQueryRequest{
Query: &pb.Query{
Kind: []*pb.KindExpression{&pb.KindExpression{Name: proto.String("Gopher")}},
},
}
if !proto.Equal(in, expectedIn) {
return fmt.Errorf("unsupported argument: got %v want %v", in, expectedIn)
}
*out = pb.RunQueryResponse{
Batch: &pb.QueryResultBatch{
MoreResults: pb.QueryResultBatch_NO_MORE_RESULTS.Enum(),
EntityResultType: pb.EntityResult_FULL.Enum(),
EntityResult: []*pb.EntityResult{
&pb.EntityResult{
Entity: &pb.Entity{
Key: key1,
Property: []*pb.Property{
{
Name: proto.String("Name"),
Value: &pb.Value{StringValue: proto.String("George")},
},
{
Name: proto.String("Height"),
Value: &pb.Value{
IntegerValue: proto.Int64(32),
},
},
},
},
},
&pb.EntityResult{
Entity: &pb.Entity{
Key: key2,
Property: []*pb.Property{
{
Name: proto.String("Name"),
Value: &pb.Value{StringValue: proto.String("Rufus")},
},
// No height for Rufus.
},
},
},
},
},
}
return nil
}
type StructThatImplementsPLS struct{}
func (StructThatImplementsPLS) Load(p []Property) error { return nil }
func (StructThatImplementsPLS) Save() ([]Property, error) { return nil, nil }
var _ PropertyLoadSaver = StructThatImplementsPLS{}
type StructPtrThatImplementsPLS struct{}
func (*StructPtrThatImplementsPLS) Load(p []Property) error { return nil }
func (*StructPtrThatImplementsPLS) Save() ([]Property, error) { return nil, nil }
var _ PropertyLoadSaver = &StructPtrThatImplementsPLS{}
type PropertyMap map[string]Property
func (m PropertyMap) Load(props []Property) error {
for _, p := range props {
m[p.Name] = p
}
return nil
}
func (m PropertyMap) Save() ([]Property, error) {
props := make([]Property, 0, len(m))
for _, p := range m {
props = append(props, p)
}
return props, nil
}
var _ PropertyLoadSaver = PropertyMap{}
type Gopher struct {
Name string
Height int
}
// typeOfEmptyInterface is the type of interface{}, but we can't use
// reflect.TypeOf((interface{})(nil)) directly because TypeOf takes an
// interface{}.
var typeOfEmptyInterface = reflect.TypeOf((*interface{})(nil)).Elem()
func TestCheckMultiArg(t *testing.T) {
testCases := []struct {
v interface{}
mat multiArgType
elemType reflect.Type
}{
// Invalid cases.
{nil, multiArgTypeInvalid, nil},
{Gopher{}, multiArgTypeInvalid, nil},
{&Gopher{}, multiArgTypeInvalid, nil},
{PropertyList{}, multiArgTypeInvalid, nil}, // This is a special case.
{PropertyMap{}, multiArgTypeInvalid, nil},
{[]*PropertyList(nil), multiArgTypeInvalid, nil},
{[]*PropertyMap(nil), multiArgTypeInvalid, nil},
{[]**Gopher(nil), multiArgTypeInvalid, nil},
{[]*interface{}(nil), multiArgTypeInvalid, nil},
// Valid cases.
{
[]PropertyList(nil),
multiArgTypePropertyLoadSaver,
reflect.TypeOf(PropertyList{}),
},
{
[]PropertyMap(nil),
multiArgTypePropertyLoadSaver,
reflect.TypeOf(PropertyMap{}),
},
{
[]StructThatImplementsPLS(nil),
multiArgTypePropertyLoadSaver,
reflect.TypeOf(StructThatImplementsPLS{}),
},
{
[]StructPtrThatImplementsPLS(nil),
multiArgTypePropertyLoadSaver,
reflect.TypeOf(StructPtrThatImplementsPLS{}),
},
{
[]Gopher(nil),
multiArgTypeStruct,
reflect.TypeOf(Gopher{}),
},
{
[]*Gopher(nil),
multiArgTypeStructPtr,
reflect.TypeOf(Gopher{}),
},
{
[]interface{}(nil),
multiArgTypeInterface,
typeOfEmptyInterface,
},
}
for _, tc := range testCases {
mat, elemType := checkMultiArg(reflect.ValueOf(tc.v))
if mat != tc.mat || elemType != tc.elemType {
t.Errorf("checkMultiArg(%T): got %v, %v want %v, %v",
tc.v, mat, elemType, tc.mat, tc.elemType)
}
}
}
func TestSimpleQuery(t *testing.T) {
struct1 := Gopher{Name: "George", Height: 32}
struct2 := Gopher{Name: "Rufus"}
pList1 := PropertyList{
{
Name: "Name",
Value: "George",
},
{
Name: "Height",
Value: int64(32),
},
}
pList2 := PropertyList{
{
Name: "Name",
Value: "Rufus",
},
}
pMap1 := PropertyMap{
"Name": Property{
Name: "Name",
Value: "George",
},
"Height": Property{
Name: "Height",
Value: int64(32),
},
}
pMap2 := PropertyMap{
"Name": Property{
Name: "Name",
Value: "Rufus",
},
}
testCases := []struct {
dst interface{}
want interface{}
}{
// The destination must have type *[]P, *[]S or *[]*S, for some non-interface
// type P such that *P implements PropertyLoadSaver, or for some struct type S.
{new([]Gopher), &[]Gopher{struct1, struct2}},
{new([]*Gopher), &[]*Gopher{&struct1, &struct2}},
{new([]PropertyList), &[]PropertyList{pList1, pList2}},
{new([]PropertyMap), &[]PropertyMap{pMap1, pMap2}},
// Any other destination type is invalid.
{0, nil},
{Gopher{}, nil},
{PropertyList{}, nil},
{PropertyMap{}, nil},
{[]int{}, nil},
{[]Gopher{}, nil},
{[]PropertyList{}, nil},
{new(int), nil},
{new(Gopher), nil},
{new(PropertyList), nil}, // This is a special case.
{new(PropertyMap), nil},
{new([]int), nil},
{new([]map[int]int), nil},
{new([]map[string]Property), nil},
{new([]map[string]interface{}), nil},
{new([]*int), nil},
{new([]*map[int]int), nil},
{new([]*map[string]Property), nil},
{new([]*map[string]interface{}), nil},
{new([]**Gopher), nil},
{new([]*PropertyList), nil},
{new([]*PropertyMap), nil},
}
for _, tc := range testCases {
nCall := 0
client := &Client{
client: fakeClient(func(in, out proto.Message) error {
nCall++
return fakeRunQuery(in.(*pb.RunQueryRequest), out.(*pb.RunQueryResponse))
}),
}
ctx := context.Background()
var (
expectedErr error
expectedNCall int
)
if tc.want == nil {
expectedErr = ErrInvalidEntityType
} else {
expectedNCall = 1
}
keys, err := client.GetAll(ctx, NewQuery("Gopher"), tc.dst)
if err != expectedErr {
t.Errorf("dst type %T: got error %v, want %v", tc.dst, err, expectedErr)
continue
}
if nCall != expectedNCall {
t.Errorf("dst type %T: Context.Call was called an incorrect number of times: got %d want %d", tc.dst, nCall, expectedNCall)
continue
}
if err != nil {
continue
}
key1 := NewKey(ctx, "Gopher", "", 6, nil)
expectedKeys := []*Key{
key1,
NewKey(ctx, "Gopher", "", 8, key1),
}
if l1, l2 := len(keys), len(expectedKeys); l1 != l2 {
t.Errorf("dst type %T: got %d keys, want %d keys", tc.dst, l1, l2)
continue
}
for i, key := range keys {
if !keysEqual(key, expectedKeys[i]) {
t.Errorf("dst type %T: got key #%d %v, want %v", tc.dst, i, key, expectedKeys[i])
continue
}
}
if !reflect.DeepEqual(tc.dst, tc.want) {
t.Errorf("dst type %T: Entities got %+v, want %+v", tc.dst, tc.dst, tc.want)
continue
}
}
}
// keysEqual is like (*Key).Equal, but ignores the App ID.
func keysEqual(a, b *Key) bool {
for a != nil && b != nil {
if a.Kind() != b.Kind() || a.Name() != b.Name() || a.ID() != b.ID() {
return false
}
a, b = a.Parent(), b.Parent()
}
return a == b
}
func TestQueriesAreImmutable(t *testing.T) {
// Test that deriving q2 from q1 does not modify q1.
q0 := NewQuery("foo")
q1 := NewQuery("foo")
q2 := q1.Offset(2)
if !reflect.DeepEqual(q0, q1) {
t.Errorf("q0 and q1 were not equal")
}
if reflect.DeepEqual(q1, q2) {
t.Errorf("q1 and q2 were equal")
}
// Test that deriving from q4 twice does not conflict, even though
// q4 has a long list of order clauses. This tests that the arrays
// backed by a query's slice of orders are not shared.
f := func() *Query {
q := NewQuery("bar")
// 47 is an ugly number that is unlikely to be near a re-allocation
// point in repeated append calls. For example, it's not near a power
// of 2 or a multiple of 10.
for i := 0; i < 47; i++ {
q = q.Order(fmt.Sprintf("x%d", i))
}
return q
}
q3 := f().Order("y")
q4 := f()
q5 := q4.Order("y")
q6 := q4.Order("z")
if !reflect.DeepEqual(q3, q5) {
t.Errorf("q3 and q5 were not equal")
}
if reflect.DeepEqual(q5, q6) {
t.Errorf("q5 and q6 were equal")
}
}
func TestFilterParser(t *testing.T) {
testCases := []struct {
filterStr string
wantOK bool
wantFieldName string
wantOp operator
}{
// Supported ops.
{"x<", true, "x", lessThan},
{"x <", true, "x", lessThan},
{"x <", true, "x", lessThan},
{" x < ", true, "x", lessThan},
{"x <=", true, "x", lessEq},
{"x =", true, "x", equal},
{"x >=", true, "x", greaterEq},
{"x >", true, "x", greaterThan},
{"in >", true, "in", greaterThan},
{"in>", true, "in", greaterThan},
// Valid but (currently) unsupported ops.
{"x!=", false, "", 0},
{"x !=", false, "", 0},
{" x != ", false, "", 0},
{"x IN", false, "", 0},
{"x in", false, "", 0},
// Invalid ops.
{"x EQ", false, "", 0},
{"x lt", false, "", 0},
{"x <>", false, "", 0},
{"x >>", false, "", 0},
{"x ==", false, "", 0},
{"x =<", false, "", 0},
{"x =>", false, "", 0},
{"x !", false, "", 0},
{"x ", false, "", 0},
{"x", false, "", 0},
// Quoted and interesting field names.
{"x > y =", true, "x > y", equal},
{"` x ` =", true, " x ", equal},
{`" x " =`, true, " x ", equal},
{`" \"x " =`, true, ` "x `, equal},
{`" x =`, false, "", 0},
{`" x ="`, false, "", 0},
{"` x \" =", false, "", 0},
}
for _, tc := range testCases {
q := NewQuery("foo").Filter(tc.filterStr, 42)
if ok := q.err == nil; ok != tc.wantOK {
t.Errorf("%q: ok=%t, want %t", tc.filterStr, ok, tc.wantOK)
continue
}
if !tc.wantOK {
continue
}
if len(q.filter) != 1 {
t.Errorf("%q: len=%d, want %d", tc.filterStr, len(q.filter), 1)
continue
}
got, want := q.filter[0], filter{tc.wantFieldName, tc.wantOp, 42}
if got != want {
t.Errorf("%q: got %v, want %v", tc.filterStr, got, want)
continue
}
}
}
func TestNamespaceQuery(t *testing.T) {
gotNamespace := make(chan string, 1)
ctx := context.Background()
client := &Client{
client: fakeClient(func(req, resp proto.Message) error {
gotNamespace <- req.(*pb.RunQueryRequest).GetPartitionId().GetNamespace()
return errors.New("not implemented")
}),
}
var gs []Gopher
client.GetAll(ctx, NewQuery("gopher"), &gs)
if got, want := <-gotNamespace, ""; got != want {
t.Errorf("GetAll: got namespace %q, want %q", got, want)
}
client.Count(ctx, NewQuery("gopher"))
if got, want := <-gotNamespace, ""; got != want {
t.Errorf("Count: got namespace %q, want %q", got, want)
}
const ns = "not_default"
ctx = WithNamespace(ctx, ns)
client.GetAll(ctx, NewQuery("gopher"), &gs)
if got, want := <-gotNamespace, ns; got != want {
t.Errorf("GetAll: got namespace %q, want %q", got, want)
}
client.Count(ctx, NewQuery("gopher"))
if got, want := <-gotNamespace, ns; got != want {
t.Errorf("Count: got namespace %q, want %q", got, want)
}
}

210
vendor/google.golang.org/cloud/datastore/save.go generated vendored Normal file
View file

@ -0,0 +1,210 @@
// Copyright 2014 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 (
"errors"
"fmt"
"reflect"
"time"
"github.com/golang/protobuf/proto"
pb "google.golang.org/cloud/internal/datastore"
)
// saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer.
func saveEntity(key *Key, src interface{}) (*pb.Entity, error) {
var err error
var props []Property
if e, ok := src.(PropertyLoadSaver); ok {
props, err = e.Save()
} else {
props, err = SaveStruct(src)
}
if err != nil {
return nil, err
}
return propertiesToProto(key, props)
}
func saveStructProperty(props *[]Property, name string, noIndex, multiple bool, v reflect.Value) error {
p := Property{
Name: name,
NoIndex: noIndex,
Multiple: multiple,
}
switch x := v.Interface().(type) {
case *Key, time.Time:
p.Value = x
default:
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p.Value = v.Int()
case reflect.Bool:
p.Value = v.Bool()
case reflect.String:
p.Value = v.String()
case reflect.Float32, reflect.Float64:
p.Value = v.Float()
case reflect.Slice:
if v.Type().Elem().Kind() == reflect.Uint8 {
p.Value = v.Bytes()
}
case reflect.Struct:
if !v.CanAddr() {
return fmt.Errorf("datastore: unsupported struct field: value is unaddressable")
}
sub, err := newStructPLS(v.Addr().Interface())
if err != nil {
return fmt.Errorf("datastore: unsupported struct field: %v", err)
}
return sub.(structPLS).save(props, name, noIndex, multiple)
}
}
if p.Value == nil {
return fmt.Errorf("datastore: unsupported struct field type: %v", v.Type())
}
*props = append(*props, p)
return nil
}
func (s structPLS) Save() ([]Property, error) {
var props []Property
if err := s.save(&props, "", false, false); err != nil {
return nil, err
}
return props, nil
}
func (s structPLS) save(props *[]Property, prefix string, noIndex, multiple bool) error {
for i, t := range s.codec.byIndex {
if t.name == "-" {
continue
}
name := t.name
if prefix != "" {
name = prefix + name
}
v := s.v.Field(i)
if !v.IsValid() || !v.CanSet() {
continue
}
noIndex1 := noIndex || t.noIndex
// For slice fields that aren't []byte, save each element.
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
for j := 0; j < v.Len(); j++ {
if err := saveStructProperty(props, name, noIndex1, true, v.Index(j)); err != nil {
return err
}
}
continue
}
// Otherwise, save the field itself.
if err := saveStructProperty(props, name, noIndex1, multiple, v); err != nil {
return err
}
}
return nil
}
func propertiesToProto(key *Key, props []Property) (*pb.Entity, error) {
e := &pb.Entity{
Key: keyToProto(key),
}
indexedProps := 0
prevMultiple := make(map[string]*pb.Property)
for _, p := range props {
val, err := interfaceToProto(p.Value)
if err != "" {
return nil, fmt.Errorf("datastore: %s for a Property with Name %q", err, p.Name)
}
if !p.NoIndex {
rVal := reflect.ValueOf(p.Value)
if rVal.Kind() == reflect.Slice && rVal.Type().Elem().Kind() != reflect.Uint8 {
indexedProps += rVal.Len()
} else {
indexedProps++
}
}
if indexedProps > maxIndexedProperties {
return nil, errors.New("datastore: too many indexed properties")
}
switch v := p.Value.(type) {
case string:
case []byte:
if len(v) > 1500 && !p.NoIndex {
return nil, fmt.Errorf("datastore: cannot index a Property with Name %q", p.Name)
}
}
val.Indexed = proto.Bool(!p.NoIndex)
if p.Multiple {
x, ok := prevMultiple[p.Name]
if !ok {
x = &pb.Property{
Name: proto.String(p.Name),
Value: &pb.Value{},
}
prevMultiple[p.Name] = x
e.Property = append(e.Property, x)
}
x.Value.ListValue = append(x.Value.ListValue, val)
} else {
e.Property = append(e.Property, &pb.Property{
Name: proto.String(p.Name),
Value: val,
})
}
}
return e, nil
}
func interfaceToProto(iv interface{}) (p *pb.Value, errStr string) {
val := new(pb.Value)
switch v := iv.(type) {
case int:
val.IntegerValue = proto.Int64(int64(v))
case int32:
val.IntegerValue = proto.Int64(int64(v))
case int64:
val.IntegerValue = proto.Int64(v)
case bool:
val.BooleanValue = proto.Bool(v)
case string:
val.StringValue = proto.String(v)
case float32:
val.DoubleValue = proto.Float64(float64(v))
case float64:
val.DoubleValue = proto.Float64(v)
case *Key:
if v != nil {
val.KeyValue = keyToProto(v)
}
case time.Time:
if v.Before(minTime) || v.After(maxTime) {
return nil, fmt.Sprintf("time value out of range")
}
val.TimestampMicrosecondsValue = proto.Int64(toUnixMicro(v))
case []byte:
val.BlobValue = v
default:
if iv != nil {
return nil, fmt.Sprintf("invalid Value type %t", iv)
}
}
// TODO(jbd): Support ListValue and EntityValue.
// TODO(jbd): Support types whose underlying type is one of the types above.
return val, ""
}

View file

@ -0,0 +1,41 @@
indexes:
- kind: SQChild
ancestor: yes
properties:
- name: T
- name: I
- kind: SQChild
ancestor: yes
properties:
- name: T
- name: I
direction: desc
- kind: SQChild
ancestor: yes
properties:
- name: I
- name: T
- name: U
- kind: SQChild
ancestor: yes
properties:
- name: I
- name: T
- name: U
- kind: SQChild
ancestor: yes
properties:
- name: T
- name: J
- kind: SQChild
ancestor: yes
properties:
- name: T
- name: J
- name: U

36
vendor/google.golang.org/cloud/datastore/time.go generated vendored Normal file
View file

@ -0,0 +1,36 @@
// Copyright 2014 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 (
"math"
"time"
)
var (
minTime = time.Unix(int64(math.MinInt64)/1e6, (int64(math.MinInt64)%1e6)*1e3)
maxTime = time.Unix(int64(math.MaxInt64)/1e6, (int64(math.MaxInt64)%1e6)*1e3)
)
func toUnixMicro(t time.Time) int64 {
// We cannot use t.UnixNano() / 1e3 because we want to handle times more than
// 2^63 nanoseconds (which is about 292 years) away from 1970, and those cannot
// be represented in the numerator of a single int64 divide.
return t.Unix()*1e6 + int64(t.Nanosecond()/1e3)
}
func fromUnixMicro(t int64) time.Time {
return time.Unix(t/1e6, (t%1e6)*1e3)
}

75
vendor/google.golang.org/cloud/datastore/time_test.go generated vendored Normal file
View file

@ -0,0 +1,75 @@
// Copyright 2014 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"
"time"
)
func TestUnixMicro(t *testing.T) {
// Test that all these time.Time values survive a round trip to unix micros.
testCases := []time.Time{
{},
time.Date(2, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(23, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(234, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(1600, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(1700, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(1800, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC),
time.Unix(-1e6, -1000),
time.Unix(-1e6, 0),
time.Unix(-1e6, +1000),
time.Unix(-60, -1000),
time.Unix(-60, 0),
time.Unix(-60, +1000),
time.Unix(-1, -1000),
time.Unix(-1, 0),
time.Unix(-1, +1000),
time.Unix(0, -3000),
time.Unix(0, -2000),
time.Unix(0, -1000),
time.Unix(0, 0),
time.Unix(0, +1000),
time.Unix(0, +2000),
time.Unix(+60, -1000),
time.Unix(+60, 0),
time.Unix(+60, +1000),
time.Unix(+1e6, -1000),
time.Unix(+1e6, 0),
time.Unix(+1e6, +1000),
time.Date(1999, 12, 31, 23, 59, 59, 999000, time.UTC),
time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(2006, 1, 2, 15, 4, 5, 678000, time.UTC),
time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
time.Date(3456, 1, 1, 0, 0, 0, 0, time.UTC),
}
for _, tc := range testCases {
got := fromUnixMicro(toUnixMicro(tc))
if !got.Equal(tc) {
t.Errorf("got %q, want %q", got, tc)
}
}
// Test that a time.Time that isn't an integral number of microseconds
// is not perfectly reconstructed after a round trip.
t0 := time.Unix(0, 123)
t1 := fromUnixMicro(toUnixMicro(t0))
if t1.Nanosecond()%1000 != 0 || t0.Nanosecond()%1000 == 0 {
t.Errorf("quantization to µs: got %q with %d ns, started with %d ns", t1, t1.Nanosecond(), t0.Nanosecond())
}
}

241
vendor/google.golang.org/cloud/datastore/transaction.go generated vendored Normal file
View file

@ -0,0 +1,241 @@
// Copyright 2014 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 (
"errors"
"net/http"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
pb "google.golang.org/cloud/internal/datastore"
"google.golang.org/cloud/internal/transport"
)
// ErrConcurrentTransaction is returned when a transaction is rolled back due
// to a conflict with a concurrent transaction.
var ErrConcurrentTransaction = errors.New("datastore: concurrent transaction")
var errExpiredTransaction = errors.New("datastore: transaction expired")
// A TransactionOption configures the Transaction returned by NewTransaction.
type TransactionOption interface {
apply(*pb.BeginTransactionRequest)
}
type isolation struct {
level pb.BeginTransactionRequest_IsolationLevel
}
func (i isolation) apply(req *pb.BeginTransactionRequest) {
req.IsolationLevel = i.level.Enum()
}
var (
// Snapshot causes the transaction to enforce a snapshot isolation level.
Snapshot TransactionOption = isolation{pb.BeginTransactionRequest_SNAPSHOT}
// Serializable causes the transaction to enforce a serializable isolation level.
Serializable TransactionOption = isolation{pb.BeginTransactionRequest_SERIALIZABLE}
)
// Transaction represents a set of datastore operations to be committed atomically.
//
// Operations are enqueued by calling the Put and Delete methods on Transaction
// (or their Multi-equivalents). These operations are only committed when the
// Commit method is invoked. To ensure consistency, reads must be performed by
// using Transaction's Get method or by using the Transaction method when
// building a query.
//
// A Transaction must be committed or rolled back exactly once.
type Transaction struct {
id []byte
client *Client
ctx context.Context
mutation *pb.Mutation // The mutations to apply.
pending []*PendingKey // Incomplete keys pending transaction completion.
}
// NewTransaction starts a new transaction.
func (c *Client) NewTransaction(ctx context.Context, opts ...TransactionOption) (*Transaction, error) {
req, resp := &pb.BeginTransactionRequest{}, &pb.BeginTransactionResponse{}
for _, o := range opts {
o.apply(req)
}
if err := c.call(ctx, "beginTransaction", req, resp); err != nil {
return nil, err
}
return &Transaction{
id: resp.Transaction,
ctx: ctx,
client: c,
mutation: &pb.Mutation{},
}, nil
}
// Commit applies the enqueued operations atomically.
func (t *Transaction) Commit() (*Commit, error) {
if t.id == nil {
return nil, errExpiredTransaction
}
req := &pb.CommitRequest{
Transaction: t.id,
Mutation: t.mutation,
Mode: pb.CommitRequest_TRANSACTIONAL.Enum(),
}
t.id = nil
resp := &pb.CommitResponse{}
if err := t.client.call(t.ctx, "commit", req, resp); err != nil {
if e, ok := err.(*transport.ErrHTTP); ok && e.StatusCode == http.StatusConflict {
// TODO(jbd): Make sure that we explicitly handle the case where response
// has an HTTP 409 and the error message indicates that it's an concurrent
// transaction error.
return nil, ErrConcurrentTransaction
}
return nil, err
}
// Copy any newly minted keys into the returned keys.
if len(t.pending) != len(resp.MutationResult.InsertAutoIdKey) {
return nil, errors.New("datastore: internal error: server returned the wrong number of keys")
}
commit := &Commit{}
for i, p := range t.pending {
p.key = protoToKey(resp.MutationResult.InsertAutoIdKey[i])
p.commit = commit
}
return commit, nil
}
// Rollback abandons a pending transaction.
func (t *Transaction) Rollback() error {
if t.id == nil {
return errExpiredTransaction
}
id := t.id
t.id = nil
return t.client.call(t.ctx, "rollback", &pb.RollbackRequest{Transaction: id}, &pb.RollbackResponse{})
}
// Get is the transaction-specific version of the package function Get.
// All reads performed during the transaction will come from a single consistent
// 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 {
err := t.client.get(t.ctx, []*Key{key}, []interface{}{dst}, &pb.ReadOptions{Transaction: t.id})
if me, ok := err.(MultiError); ok {
return me[0]
}
return err
}
// GetMulti is a batch version of Get.
func (t *Transaction) GetMulti(keys []*Key, dst interface{}) error {
if t.id == nil {
return errExpiredTransaction
}
return t.client.get(t.ctx, keys, dst, &pb.ReadOptions{Transaction: t.id})
}
// Put is the transaction-specific version of the package function Put.
//
// Put returns a PendingKey which can be resolved into a Key using the
// return value from a successful Commit. If key is an incomplete key, the
// returned pending key will resolve to a unique key generated by the
// datastore.
func (t *Transaction) Put(key *Key, src interface{}) (*PendingKey, error) {
h, err := t.PutMulti([]*Key{key}, []interface{}{src})
if err != nil {
if me, ok := err.(MultiError); ok {
return nil, me[0]
}
return nil, err
}
return h[0], nil
}
// 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) {
if t.id == nil {
return nil, errExpiredTransaction
}
mutation, err := putMutation(keys, src)
if err != nil {
return nil, err
}
proto.Merge(t.mutation, mutation)
// Prepare the returned handles, pre-populating where possible.
ret := make([]*PendingKey, len(keys))
for i, key := range keys {
h := &PendingKey{}
if key.Incomplete() {
// This key will be in the final commit result.
t.pending = append(t.pending, h)
} else {
h.key = key
}
ret[i] = h
}
return ret, nil
}
// Delete is the transaction-specific version of the package function Delete.
// Delete enqueues the deletion of the entity for the given key, to be
// committed atomically upon calling Commit.
func (t *Transaction) Delete(key *Key) error {
err := t.DeleteMulti([]*Key{key})
if me, ok := err.(MultiError); ok {
return me[0]
}
return err
}
// DeleteMulti is a batch version of Delete.
func (t *Transaction) DeleteMulti(keys []*Key) error {
if t.id == nil {
return errExpiredTransaction
}
mutation, err := deleteMutation(keys)
if err != nil {
return err
}
proto.Merge(t.mutation, mutation)
return 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 {
panic("PendingKey was not created by corresponding transaction")
}
return p.key
}
// PendingKey represents the key for newly-inserted entity. It can be
// resolved into a Key by calling the Key method of Commit.
type PendingKey struct {
key *Key
commit *Commit
}

View file

@ -0,0 +1,111 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// concat_table is an example client of the bigquery client library.
// It concatenates two BigQuery tables and writes the result to another table.
package main
import (
"flag"
"fmt"
"log"
"os"
"time"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"google.golang.org/cloud/bigquery"
)
var (
project = flag.String("project", "", "The ID of a Google Cloud Platform project")
dataset = flag.String("dataset", "", "The ID of a BigQuery dataset")
src1 = flag.String("src1", "", "The ID of the first BigQuery table to concatenate")
src2 = flag.String("src2", "", "The ID of the second BigQuery table to concatenate")
dest = flag.String("dest", "", "The ID of the BigQuery table to write the result to")
pollint = flag.Duration("pollint", 10*time.Second, "Polling interval for checking job status")
)
func main() {
flag.Parse()
flagsOk := true
for _, f := range []string{"project", "dataset", "src1", "src2", "dest"} {
if flag.Lookup(f).Value.String() == "" {
fmt.Fprintf(os.Stderr, "Flag --%s is required\n", f)
flagsOk = false
}
}
if !flagsOk {
os.Exit(1)
}
if *src1 == *src2 || *src1 == *dest || *src2 == *dest {
log.Fatalf("Different values must be supplied for each of --src1, --src2 and --dest")
}
httpClient, err := google.DefaultClient(context.Background(), bigquery.Scope)
if err != nil {
log.Fatalf("Creating http client: %v", err)
}
client, err := bigquery.NewClient(httpClient, *project)
if err != nil {
log.Fatalf("Creating bigquery client: %v", err)
}
s1 := &bigquery.Table{
ProjectID: *project,
DatasetID: *dataset,
TableID: *src1,
}
s2 := &bigquery.Table{
ProjectID: *project,
DatasetID: *dataset,
TableID: *src2,
}
d := &bigquery.Table{
ProjectID: *project,
DatasetID: *dataset,
TableID: *dest,
}
// Concatenate data.
job, err := client.Copy(context.Background(), d, bigquery.Tables{s1, s2}, bigquery.WriteTruncate)
if err != nil {
log.Fatalf("Concatenating: %v", err)
}
fmt.Printf("Job for concatenation operation: %+v\n", job)
fmt.Printf("Waiting for job to complete.\n")
for range time.Tick(*pollint) {
status, err := job.Status(context.Background())
if err != nil {
fmt.Printf("Failure determining status: %v", err)
break
}
if !status.Done() {
continue
}
if err := status.Err(); err == nil {
fmt.Printf("Success\n")
} else {
fmt.Printf("Failure: %+v\n", err)
}
break
}
}

View file

@ -0,0 +1,104 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// load is an example client of the bigquery client library.
// It loads a file from Google Cloud Storage into a BigQuery table.
package main
import (
"flag"
"fmt"
"log"
"os"
"time"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"google.golang.org/cloud/bigquery"
)
var (
project = flag.String("project", "", "The ID of a Google Cloud Platform project")
dataset = flag.String("dataset", "", "The ID of a BigQuery dataset")
table = flag.String("table", "", "The ID of a BigQuery table to load data into")
bucket = flag.String("bucket", "", "The name of a Google Cloud Storage bucket to load data from")
object = flag.String("object", "", "The name of a Google Cloud Storage object to load data from. Must exist within the bucket specified by --bucket")
skiprows = flag.Int64("skiprows", 0, "The number of rows of the source data to skip when loading")
pollint = flag.Duration("pollint", 10*time.Second, "Polling interval for checking job status")
)
func main() {
flag.Parse()
flagsOk := true
for _, f := range []string{"project", "dataset", "table", "bucket", "object"} {
if flag.Lookup(f).Value.String() == "" {
fmt.Fprintf(os.Stderr, "Flag --%s is required\n", f)
flagsOk = false
}
}
if !flagsOk {
os.Exit(1)
}
httpClient, err := google.DefaultClient(context.Background(), bigquery.Scope)
if err != nil {
log.Fatalf("Creating http client: %v", err)
}
client, err := bigquery.NewClient(httpClient, *project)
if err != nil {
log.Fatalf("Creating bigquery client: %v", err)
}
table := &bigquery.Table{
ProjectID: *project,
DatasetID: *dataset,
TableID: *table,
}
gcs := client.NewGCSReference(fmt.Sprintf("gs://%s/%s", *bucket, *object))
gcs.SkipLeadingRows = *skiprows
// Load data from Google Cloud Storage into a BigQuery table.
job, err := client.Copy(
context.Background(), table, gcs,
bigquery.MaxBadRecords(1),
bigquery.AllowQuotedNewlines(),
bigquery.WriteTruncate)
if err != nil {
log.Fatalf("Loading data: %v", err)
}
fmt.Printf("Job for data load operation: %+v\n", job)
fmt.Printf("Waiting for job to complete.\n")
for range time.Tick(*pollint) {
status, err := job.Status(context.Background())
if err != nil {
fmt.Printf("Failure determining status: %v", err)
break
}
if !status.Done() {
continue
}
if err := status.Err(); err == nil {
fmt.Printf("Success\n")
} else {
fmt.Printf("Failure: %+v\n", err)
}
break
}
}

View file

@ -0,0 +1,108 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// query is an example client of the bigquery client library.
// It submits a query and writes the result to a table.
package main
import (
"flag"
"fmt"
"log"
"os"
"time"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"google.golang.org/cloud/bigquery"
)
var (
project = flag.String("project", "", "The ID of a Google Cloud Platform project")
dataset = flag.String("dataset", "", "The ID of a BigQuery dataset")
q = flag.String("q", "", "The query string")
dest = flag.String("dest", "", "The ID of the BigQuery table to write the result to. If unset, an ephemeral table ID will be generated.")
pollint = flag.Duration("pollint", 10*time.Second, "Polling interval for checking job status")
wait = flag.Bool("wait", false, "Whether to wait for the query job to complete.")
)
func main() {
flag.Parse()
flagsOk := true
for _, f := range []string{"project", "dataset", "q"} {
if flag.Lookup(f).Value.String() == "" {
fmt.Fprintf(os.Stderr, "Flag --%s is required\n", f)
flagsOk = false
}
}
if !flagsOk {
os.Exit(1)
}
httpClient, err := google.DefaultClient(context.Background(), bigquery.Scope)
if err != nil {
log.Fatalf("Creating http client: %v", err)
}
client, err := bigquery.NewClient(httpClient, *project)
if err != nil {
log.Fatalf("Creating bigquery client: %v", err)
}
d := &bigquery.Table{}
if *dest != "" {
d.ProjectID = *project
d.DatasetID = *dataset
d.TableID = *dest
}
query := &bigquery.Query{
Q: *q,
DefaultProjectID: *project,
DefaultDatasetID: *dataset,
}
// Query data.
job, err := client.Copy(context.Background(), d, query, bigquery.WriteTruncate)
if err != nil {
log.Fatalf("Querying: %v", err)
}
fmt.Printf("Submitted query. Job ID: %s\n", job.ID())
if !*wait {
return
}
fmt.Printf("Waiting for job to complete.\n")
for range time.Tick(*pollint) {
status, err := job.Status(context.Background())
if err != nil {
fmt.Printf("Failure determining status: %v", err)
break
}
if !status.Done() {
continue
}
if err := status.Err(); err == nil {
fmt.Printf("Success\n")
} else {
fmt.Printf("Failure: %+v\n", err)
}
break
}
}

View file

@ -0,0 +1,148 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// read is an example client of the bigquery client library.
// It reads from a table, returning the data via an Iterator.
package main
import (
"flag"
"fmt"
"log"
"os"
"regexp"
"strings"
"text/tabwriter"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"google.golang.org/cloud/bigquery"
)
var (
project = flag.String("project", "", "The ID of a Google Cloud Platform project")
dataset = flag.String("dataset", "", "The ID of a BigQuery dataset")
table = flag.String("table", ".*", "A regular expression to match the IDs of tables to read.")
jobID = flag.String("jobid", "", "The ID of a query job that has already been submitted."+
" If set, --dataset, --table will be ignored, and results will be read from the specified job.")
)
func printValues(it *bigquery.Iterator) {
// one-space padding.
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
for it.Next(context.Background()) {
var vals bigquery.ValueList
if err := it.Get(&vals); err != nil {
fmt.Printf("err calling get: %v\n", err)
} else {
sep := ""
for _, v := range vals {
fmt.Fprintf(tw, "%s%v", sep, v)
sep = "\t"
}
fmt.Fprintf(tw, "\n")
}
}
tw.Flush()
fmt.Printf("\n")
if err := it.Err(); err != nil {
fmt.Printf("err reading: %v\n", err)
}
}
func printTable(client *bigquery.Client, t *bigquery.Table) {
it, err := client.Read(context.Background(), t)
if err != nil {
log.Fatalf("Reading: %v", err)
}
id := t.FullyQualifiedName()
fmt.Printf("%s\n%s\n", id, strings.Repeat("-", len(id)))
printValues(it)
}
func printQueryResults(client *bigquery.Client, queryJobID string) {
job, err := client.JobFromID(context.Background(), queryJobID)
if err != nil {
log.Fatalf("Loading job: %v", err)
}
it, err := client.Read(context.Background(), job)
if err != nil {
log.Fatalf("Reading: %v", err)
}
// TODO: print schema.
printValues(it)
}
func main() {
flag.Parse()
flagsOk := true
if flag.Lookup("project").Value.String() == "" {
fmt.Fprintf(os.Stderr, "Flag --project is required\n")
flagsOk = false
}
var sourceFlagCount int
if flag.Lookup("dataset").Value.String() != "" {
sourceFlagCount++
}
if flag.Lookup("jobid").Value.String() != "" {
sourceFlagCount++
}
if sourceFlagCount != 1 {
fmt.Fprintf(os.Stderr, "Exactly one of --dataset or --jobid must be set\n")
flagsOk = false
}
if !flagsOk {
os.Exit(1)
}
tableRE, err := regexp.Compile(*table)
if err != nil {
fmt.Fprintf(os.Stderr, "--table is not a valid regular expression: %q\n", *table)
os.Exit(1)
}
httpClient, err := google.DefaultClient(context.Background(), bigquery.Scope)
if err != nil {
log.Fatalf("Creating http client: %v", err)
}
client, err := bigquery.NewClient(httpClient, *project)
if err != nil {
log.Fatalf("Creating bigquery client: %v", err)
}
if *jobID != "" {
printQueryResults(client, *jobID)
return
}
ds := client.Dataset(*dataset)
var tables []*bigquery.Table
tables, err = ds.ListTables(context.Background())
if err != nil {
log.Fatalf("Listing tables: %v", err)
}
for _, t := range tables {
if tableRE.MatchString(t.TableID) {
printTable(client, t)
}
}
}

View file

@ -0,0 +1,30 @@
# Cloud Bigtable on Managed VMs using Go
# (Hello World for Cloud Bigtable)
This app counts how often each user visits.
## Prerequisites
1. Set up Cloud Console.
1. Go to the [Cloud Console](https://cloud.google.com/console) and create or select your project.
You will need the project ID later.
1. Go to **Settings > Project Billing Settings** and enable billing.
1. Select **APIs & Auth > APIs**.
1. Enable the **Cloud Bigtable API** and the **Cloud Bigtable Admin API**.
(You may need to search for the API).
1. Set up gcloud.
1. `gcloud components update`
1. `gcloud auth login`
1. `gcloud config set project PROJECT_ID`
1. Download App Engine SDK for Go.
1. `go get -u google.golang.org/appengine/...`
1. In helloworld.go, change the constants `project`, `zone` and `cluster`
## Running locally
1. From the sample project folder, `gcloud preview app run app.yaml`
## Deploying on Google App Engine Managed VM
1. Install and start [Docker](https://cloud.google.com/appengine/docs/managed-vms/getting-started#install_docker).
1. From the sample project folder, `aedeploy gcloud preview app deploy app.yaml`

View file

@ -0,0 +1,11 @@
runtime: go
api_version: go1
vm: true
manual_scaling:
instances: 1
handlers:
# Serve only the web root.
- url: /
script: _go_app

View file

@ -0,0 +1,169 @@
// Copyright 2015 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
helloworld tracks how often a user has visited the index page.
This program demonstrates usage of the Cloud Bigtable API for Managed VMs and Go.
Instructions for running this program are in the README.md.
*/
package main
import (
"bytes"
"encoding/binary"
"html/template"
"log"
"net/http"
"golang.org/x/net/context"
"google.golang.org/appengine"
aelog "google.golang.org/appengine/log"
"google.golang.org/appengine/user"
"google.golang.org/cloud/bigtable"
)
// User-provided constants.
const (
project = "PROJECT_ID"
zone = "CLUSTER_ZONE"
cluster = "CLUSTER_NAME"
)
var (
tableName = "bigtable-hello"
familyName = "emails"
// Client is initialized by main.
client *bigtable.Client
)
func main() {
ctx := context.Background()
// Set up admin client, tables, and column families.
// NewAdminClient uses Application Default Credentials to authenticate.
adminClient, err := bigtable.NewAdminClient(ctx, project, zone, cluster)
if err != nil {
log.Fatalf("Unable to create a table admin client. %v", err)
}
tables, err := adminClient.Tables(ctx)
if err != nil {
log.Fatalf("Unable to fetch table list. %v", err)
}
if !sliceContains(tables, tableName) {
if err := adminClient.CreateTable(ctx, tableName); err != nil {
log.Fatalf("Unable to create table: %v. %v", tableName, err)
}
}
tblInfo, err := adminClient.TableInfo(ctx, tableName)
if err != nil {
log.Fatalf("Unable to read info for table: %v. %v", tableName, err)
}
if !sliceContains(tblInfo.Families, familyName) {
if err := adminClient.CreateColumnFamily(ctx, tableName, familyName); err != nil {
log.Fatalf("Unable to create column family: %v. %v", familyName, err)
}
}
adminClient.Close()
// Set up Bigtable data operations client.
// NewClient uses Application Default Credentials to authenticate.
client, err = bigtable.NewClient(ctx, project, zone, cluster)
if err != nil {
log.Fatalf("Unable to create data operations client. %v", err)
}
http.Handle("/", appHandler(mainHandler))
appengine.Main() // Never returns.
}
// mainHandler tracks how many times each user has visited this page.
func mainHandler(w http.ResponseWriter, r *http.Request) *appError {
if r.URL.Path != "/" {
http.NotFound(w, r)
return nil
}
ctx := appengine.NewContext(r)
u := user.Current(ctx)
if u == nil {
login, err := user.LoginURL(ctx, r.URL.String())
if err != nil {
return &appError{err, "Error finding login URL", http.StatusInternalServerError}
}
http.Redirect(w, r, login, http.StatusFound)
return nil
}
logoutURL, err := user.LogoutURL(ctx, "/")
if err != nil {
return &appError{err, "Error finding logout URL", http.StatusInternalServerError}
}
// Display hello page.
tbl := client.Open(tableName)
rmw := bigtable.NewReadModifyWrite()
rmw.Increment(familyName, u.Email, 1)
row, err := tbl.ApplyReadModifyWrite(ctx, u.Email, rmw)
if err != nil {
return &appError{err, "Error applying ReadModifyWrite to row: " + u.Email, http.StatusInternalServerError}
}
data := struct {
Username, Logout string
Visits uint64
}{
Username: u.Email,
// Retrieve the most recently edited column.
Visits: binary.BigEndian.Uint64(row[familyName][0].Value),
Logout: logoutURL,
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return &appError{err, "Error writing template", http.StatusInternalServerError}
}
buf.WriteTo(w)
return nil
}
var tmpl = template.Must(template.New("").Parse(`
<html><body>
<p>
{{with .Username}} Hello {{.}}{{end}}
{{with .Logout}}<a href="{{.}}">Sign out</a>{{end}}
</p>
<p>
You have visited {{.Visits}}
</p>
</body></html>`))
// sliceContains reports whether the provided string is present in the given slice of strings.
func sliceContains(list []string, target string) bool {
for _, s := range list {
if s == target {
return true
}
}
return false
}
// More info about this method of error handling can be found at: http://blog.golang.org/error-handling-and-go
type appHandler func(http.ResponseWriter, *http.Request) *appError
type appError struct {
Error error
Message string
Code int
}
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil {
ctx := appengine.NewContext(r)
aelog.Errorf(ctx, "%v", e.Error)
http.Error(w, e.Message, e.Code)
}
}

View file

@ -0,0 +1,439 @@
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// This is a sample web server that uses Cloud Bigtable as the storage layer
// for a simple document-storage and full-text-search service.
// It has three functions:
// - Add a document. This adds the content of a user-supplied document to the
// Bigtable, and adds references to the document to an index in the Bigtable.
// The document is indexed under each unique word in the document.
// - Search the index. This returns documents containing each word in a user
// query, with snippets and links to view the whole document.
// - Clear the table. This deletes and recreates the Bigtable,
package main
import (
"bytes"
"flag"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"strings"
"sync"
"time"
"unicode"
"golang.org/x/net/context"
"google.golang.org/cloud/bigtable"
)
var (
project = flag.String("project", "", "The name of the project.")
zone = flag.String("zone", "", "The zone of the project.")
cluster = flag.String("cluster", "", "The name of the Cloud Bigtable cluster.")
tableName = flag.String("table", "docindex", "The name of the table containing the documents and index.")
credFile = flag.String("creds", "", "File containing credentials")
rebuild = flag.Bool("rebuild", false, "Rebuild the table from scratch on startup.")
client *bigtable.Client
adminClient *bigtable.AdminClient
table *bigtable.Table
addTemplate = template.Must(template.New("").Parse(`<html><body>
Added {{.Title}}
</body></html>`))
contentTemplate = template.Must(template.New("").Parse(`<html><body>
<b>{{.Title}}</b><br><br>
{{.Content}}
</body></html>`))
searchTemplate = template.Must(template.New("").Parse(`<html><body>
Results for <b>{{.Query}}</b>:<br><br>
{{range .Results}}
<a href="/content?name={{.Title}}">{{.Title}}</a><br>
<i>{{.Snippet}}</i><br><br>
{{end}}
</body></html>`))
)
const (
// prototypeTableName is an existing table containing some documents.
// Rebuilding a table will populate it with the data from this table.
prototypeTableName = "shakespearetemplate"
indexColumnFamily = "i"
contentColumnFamily = "c"
mainPage = `
<html>
<head>
<title>Document Search</title>
</head>
<body>
Search for documents:
<form action="/search" method="post">
<div><input type="text" name="q" size=80></div>
<div><input type="submit" value="Search"></div>
</form>
Add a document:
<form action="/add" method="post">
Document name:
<div><textarea name="name" rows="1" cols="80"></textarea></div>
Document text:
<div><textarea name="content" rows="20" cols="80"></textarea></div>
<div><input type="submit" value="Submit"></div>
</form>
Rebuild table:
<form action="/clearindex" method="post">
<div><input type="submit" value="Rebuild"></div>
</form>
</body>
</html>
`
)
func main() {
flag.Parse()
if *tableName == prototypeTableName {
log.Fatal("Can't use " + prototypeTableName + " as your table.")
}
// Let the library get credentials from file.
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", *credFile)
// Make an admin client.
var err error
if adminClient, err = bigtable.NewAdminClient(context.Background(), *project, *zone, *cluster); err != nil {
log.Fatal("Bigtable NewAdminClient:", err)
}
// Make a regular client.
client, err = bigtable.NewClient(context.Background(), *project, *zone, *cluster)
if err != nil {
log.Fatal("Bigtable NewClient:", err)
}
// Open the table.
table = client.Open(*tableName)
// Rebuild the table if the command-line flag is set.
if *rebuild {
if err := rebuildTable(); err != nil {
log.Fatal(err)
}
}
// Set up HTML handlers, and start the web server.
http.HandleFunc("/search", handleSearch)
http.HandleFunc("/content", handleContent)
http.HandleFunc("/add", handleAddDoc)
http.HandleFunc("/clearindex", handleClear)
http.HandleFunc("/", handleMain)
log.Fatal(http.ListenAndServe(":8080", nil))
}
// handleMain outputs the home page, containing a search box, an "add document" box, and "clear table" button.
func handleMain(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, mainPage)
}
// tokenize splits a string into tokens.
// This is very simple, it's not a good tokenization function.
func tokenize(s string) []string {
wordMap := make(map[string]bool)
f := strings.FieldsFunc(s, func(r rune) bool { return !unicode.IsLetter(r) })
for _, word := range f {
word = strings.ToLower(word)
wordMap[word] = true
}
words := make([]string, 0, len(wordMap))
for word := range wordMap {
words = append(words, word)
}
return words
}
// handleContent fetches the content of a document from the Bigtable and returns it.
func handleContent(w http.ResponseWriter, r *http.Request) {
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
name := r.FormValue("name")
if len(name) == 0 {
http.Error(w, "No document name supplied.", http.StatusBadRequest)
return
}
row, err := table.ReadRow(ctx, name)
if err != nil {
http.Error(w, "Error reading content: "+err.Error(), http.StatusInternalServerError)
return
}
content := row[contentColumnFamily]
if len(content) == 0 {
http.Error(w, "Document not found.", http.StatusNotFound)
return
}
var buf bytes.Buffer
if err := contentTemplate.ExecuteTemplate(&buf, "", struct{ Title, Content string }{name, string(content[0].Value)}); err != nil {
http.Error(w, "Error executing HTML template: "+err.Error(), http.StatusInternalServerError)
return
}
io.Copy(w, &buf)
}
// handleSearch responds to search queries, returning links and snippets for matching documents.
func handleSearch(w http.ResponseWriter, r *http.Request) {
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
query := r.FormValue("q")
// Split the query into words.
words := tokenize(query)
if len(words) == 0 {
http.Error(w, "Empty query.", http.StatusBadRequest)
return
}
// readRows reads from many rows concurrently.
readRows := func(rows []string) ([]bigtable.Row, error) {
results := make([]bigtable.Row, len(rows))
errors := make([]error, len(rows))
var wg sync.WaitGroup
for i, row := range rows {
wg.Add(1)
go func(i int, row string) {
defer wg.Done()
results[i], errors[i] = table.ReadRow(ctx, row)
}(i, row)
}
wg.Wait()
for _, err := range errors {
if err != nil {
return nil, err
}
}
return results, nil
}
// For each query word, get the list of documents containing it.
results, err := readRows(words)
if err != nil {
http.Error(w, "Error reading index: "+err.Error(), http.StatusInternalServerError)
return
}
// Count how many of the query words each result contained.
hits := make(map[string]int)
for _, r := range results {
for _, r := range r[indexColumnFamily] {
hits[r.Column]++
}
}
// Build a slice of all the documents that matched every query word.
var matches []string
for doc, count := range hits {
if count == len(words) {
matches = append(matches, doc[len(indexColumnFamily+":"):])
}
}
// Fetch the content of those documents from the Bigtable.
content, err := readRows(matches)
if err != nil {
http.Error(w, "Error reading results: "+err.Error(), http.StatusInternalServerError)
return
}
type result struct{ Title, Snippet string }
data := struct {
Query string
Results []result
}{query, nil}
// Output links and snippets.
for i, doc := range matches {
var text string
c := content[i][contentColumnFamily]
if len(c) > 0 {
text = string(c[0].Value)
}
if len(text) > 100 {
text = text[:100] + "..."
}
data.Results = append(data.Results, result{doc, text})
}
var buf bytes.Buffer
if err := searchTemplate.ExecuteTemplate(&buf, "", data); err != nil {
http.Error(w, "Error executing HTML template: "+err.Error(), http.StatusInternalServerError)
return
}
io.Copy(w, &buf)
}
// handleAddDoc adds a document to the index.
func handleAddDoc(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "POST requests only", http.StatusMethodNotAllowed)
return
}
ctx, _ := context.WithTimeout(context.Background(), time.Minute)
name := r.FormValue("name")
if len(name) == 0 {
http.Error(w, "Empty document name!", http.StatusBadRequest)
return
}
content := r.FormValue("content")
if len(content) == 0 {
http.Error(w, "Empty document content!", http.StatusBadRequest)
return
}
var (
writeErr error // Set if any write fails.
mu sync.Mutex // Protects writeErr
wg sync.WaitGroup // Used to wait for all writes to finish.
)
// writeOneColumn writes one column in one row, updates err if there is an error,
// and signals wg that one operation has finished.
writeOneColumn := func(row, family, column, value string, ts bigtable.Timestamp) {
mut := bigtable.NewMutation()
mut.Set(family, column, ts, []byte(value))
err := table.Apply(ctx, row, mut)
if err != nil {
mu.Lock()
writeErr = err
mu.Unlock()
}
}
// Start a write to store the document content.
wg.Add(1)
go func() {
writeOneColumn(name, contentColumnFamily, "", content, bigtable.Now())
wg.Done()
}()
// Start writes to store the document name in the index for each word in the document.
words := tokenize(content)
for _, word := range words {
var (
row = word
family = indexColumnFamily
column = name
value = ""
ts = bigtable.Now()
)
wg.Add(1)
go func() {
// TODO: should use a semaphore to limit the number of concurrent writes.
writeOneColumn(row, family, column, value, ts)
wg.Done()
}()
}
wg.Wait()
if writeErr != nil {
http.Error(w, "Error writing to Bigtable: "+writeErr.Error(), http.StatusInternalServerError)
return
}
var buf bytes.Buffer
if err := addTemplate.ExecuteTemplate(&buf, "", struct{ Title string }{name}); err != nil {
http.Error(w, "Error executing HTML template: "+err.Error(), http.StatusInternalServerError)
return
}
io.Copy(w, &buf)
}
// rebuildTable deletes the table if it exists, then creates the table, with the index column family.
func rebuildTable() error {
ctx, _ := context.WithTimeout(context.Background(), 5*time.Minute)
adminClient.DeleteTable(ctx, *tableName)
if err := adminClient.CreateTable(ctx, *tableName); err != nil {
return fmt.Errorf("CreateTable: %v", err)
}
time.Sleep(20 * time.Second)
if err := adminClient.CreateColumnFamily(ctx, *tableName, indexColumnFamily); err != nil {
return fmt.Errorf("CreateColumnFamily: %v", err)
}
if err := adminClient.CreateColumnFamily(ctx, *tableName, contentColumnFamily); err != nil {
return fmt.Errorf("CreateColumnFamily: %v", err)
}
// Open the prototype table. It contains a number of documents to get started with.
prototypeTable := client.Open(prototypeTableName)
var (
writeErr error // Set if any write fails.
mu sync.Mutex // Protects writeErr
wg sync.WaitGroup // Used to wait for all writes to finish.
)
copyRowToTable := func(row bigtable.Row) bool {
mu.Lock()
failed := writeErr != nil
mu.Unlock()
if failed {
return false
}
mut := bigtable.NewMutation()
for family, items := range row {
for _, item := range items {
// Get the column name, excluding the column family name and ':' character.
columnWithoutFamily := item.Column[len(family)+1:]
mut.Set(family, columnWithoutFamily, bigtable.Now(), item.Value)
}
}
wg.Add(1)
go func() {
// TODO: should use a semaphore to limit the number of concurrent writes.
if err := table.Apply(ctx, row.Key(), mut); err != nil {
mu.Lock()
writeErr = err
mu.Unlock()
}
wg.Done()
}()
return true
}
// Create a filter that only accepts the column families we're interested in.
filter := bigtable.FamilyFilter(indexColumnFamily + "|" + contentColumnFamily)
// Read every row from prototypeTable, and call copyRowToTable to copy it to our table.
err := prototypeTable.ReadRows(ctx, bigtable.InfiniteRange(""), copyRowToTable, bigtable.RowFilter(filter))
wg.Wait()
if err != nil {
return err
}
return writeErr
}
// handleClear calls rebuildTable
func handleClear(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "POST requests only", http.StatusMethodNotAllowed)
return
}
if err := rebuildTable(); err != nil {
http.Error(w, "Failed to rebuild index: "+err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprint(w, "Rebuilt index.\n")
}

View file

@ -0,0 +1,354 @@
// Copyright 2014 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 main contains a simple command line tool for Cloud Pub/Sub
// Cloud Pub/Sub docs: https://cloud.google.com/pubsub/docs
package main
import (
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"time"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/cloud"
"google.golang.org/cloud/compute/metadata"
"google.golang.org/cloud/pubsub"
)
var (
jsonFile = flag.String("j", "", "A path to your JSON key file for your service account downloaded from Google Developer Console, not needed if you run it on Compute Engine instances.")
projID = flag.String("p", "", "The ID of your Google Cloud project.")
reportMPS = flag.Bool("report", false, "Reports the incoming/outgoing message rate in msg/sec if set.")
size = flag.Int("size", 10, "Batch size for pull_messages and publish_messages subcommands.")
)
const (
usage = `Available arguments are:
create_topic <name>
delete_topic <name>
create_subscription <name> <linked_topic>
delete_subscription <name>
publish <topic> <message>
pull_messages <subscription> <numworkers>
publish_messages <topic> <numworkers>
`
tick = 1 * time.Second
)
func usageAndExit(msg string) {
fmt.Fprintln(os.Stderr, msg)
fmt.Println("Flags:")
flag.PrintDefaults()
fmt.Fprint(os.Stderr, usage)
os.Exit(2)
}
// Check the length of the arguments.
func checkArgs(argv []string, min int) {
if len(argv) < min {
usageAndExit("Missing arguments")
}
}
// newClient creates http.Client with a jwt service account when
// jsonFile flag is specified, otherwise by obtaining the GCE service
// account's access token.
func newClient(jsonFile string) (*http.Client, error) {
if jsonFile != "" {
jsonKey, err := ioutil.ReadFile(jsonFile)
if err != nil {
return nil, err
}
conf, err := google.JWTConfigFromJSON(jsonKey, pubsub.ScopePubSub)
if err != nil {
return nil, err
}
return conf.Client(oauth2.NoContext), nil
}
if metadata.OnGCE() {
c := &http.Client{
Transport: &oauth2.Transport{
Source: google.ComputeTokenSource(""),
},
}
if *projID == "" {
projectID, err := metadata.ProjectID()
if err != nil {
return nil, fmt.Errorf("ProjectID failed, %v", err)
}
*projID = projectID
}
return c, nil
}
return nil, errors.New("Could not create an authenticated client.")
}
func listTopics(ctx context.Context, argv []string) {
panic("listTopics not implemented yet")
}
func createTopic(ctx context.Context, argv []string) {
checkArgs(argv, 2)
topic := argv[1]
err := pubsub.CreateTopic(ctx, topic)
if err != nil {
log.Fatalf("CreateTopic failed, %v", err)
}
fmt.Printf("Topic %s was created.\n", topic)
}
func deleteTopic(ctx context.Context, argv []string) {
checkArgs(argv, 2)
topic := argv[1]
err := pubsub.DeleteTopic(ctx, topic)
if err != nil {
log.Fatalf("DeleteTopic failed, %v", err)
}
fmt.Printf("Topic %s was deleted.\n", topic)
}
func listSubscriptions(ctx context.Context, argv []string) {
panic("listSubscriptions not implemented yet")
}
func createSubscription(ctx context.Context, argv []string) {
checkArgs(argv, 3)
sub := argv[1]
topic := argv[2]
err := pubsub.CreateSub(ctx, sub, topic, 60*time.Second, "")
if err != nil {
log.Fatalf("CreateSub failed, %v", err)
}
fmt.Printf("Subscription %s was created.\n", sub)
}
func deleteSubscription(ctx context.Context, argv []string) {
checkArgs(argv, 2)
sub := argv[1]
err := pubsub.DeleteSub(ctx, sub)
if err != nil {
log.Fatalf("DeleteSub failed, %v", err)
}
fmt.Printf("Subscription %s was deleted.\n", sub)
}
func publish(ctx context.Context, argv []string) {
checkArgs(argv, 3)
topic := argv[1]
message := argv[2]
msgIDs, err := pubsub.Publish(ctx, topic, &pubsub.Message{
Data: []byte(message),
})
if err != nil {
log.Fatalf("Publish failed, %v", err)
}
fmt.Printf("Message '%s' published to a topic %s and the message id is %s\n", message, topic, msgIDs[0])
}
type reporter struct {
reportTitle string
lastC uint64
c uint64
result <-chan int
}
func (r *reporter) report() {
ticker := time.NewTicker(tick)
defer func() {
ticker.Stop()
}()
for {
select {
case <-ticker.C:
n := r.c - r.lastC
r.lastC = r.c
mps := n / uint64(tick/time.Second)
log.Printf("%s ~%d msgs/s, total: %d", r.reportTitle, mps, r.c)
case n := <-r.result:
r.c += uint64(n)
}
}
}
func ack(ctx context.Context, sub string, ackID ...string) {
err := pubsub.Ack(ctx, sub, ackID...)
if err != nil {
log.Printf("Ack failed, %v\n", err)
}
}
func pullLoop(ctx context.Context, sub string, result chan<- int) {
for {
msgs, err := pubsub.PullWait(ctx, sub, *size)
if err != nil {
log.Printf("PullWait failed, %v\n", err)
time.Sleep(5 * time.Second)
continue
}
if len(msgs) == 0 {
log.Println("Received no messages")
continue
}
if *reportMPS {
result <- len(msgs)
}
ackIDs := make([]string, len(msgs))
for i, msg := range msgs {
if !*reportMPS {
fmt.Printf("Got a message: %s\n", msg.Data)
}
ackIDs[i] = msg.AckID
}
go ack(ctx, sub, ackIDs...)
}
}
func pullMessages(ctx context.Context, argv []string) {
checkArgs(argv, 3)
sub := argv[1]
workers, err := strconv.Atoi(argv[2])
if err != nil {
log.Fatalf("Atoi failed, %v", err)
}
result := make(chan int, 1024)
for i := 0; i < int(workers); i++ {
go pullLoop(ctx, sub, result)
}
if *reportMPS {
r := reporter{reportTitle: "Received", result: result}
r.report()
} else {
select {}
}
}
func publishLoop(ctx context.Context, topic string, workerid int, result chan<- int) {
var r uint64
for {
msgs := make([]*pubsub.Message, *size)
for i := 0; i < *size; i++ {
msgs[i] = &pubsub.Message{
Data: []byte(fmt.Sprintf("Worker: %d, Round: %d, Message: %d", workerid, r, i)),
}
}
_, err := pubsub.Publish(ctx, topic, msgs...)
if err != nil {
log.Printf("Publish failed, %v\n", err)
return
}
r++
if *reportMPS {
result <- *size
}
}
}
func publishMessages(ctx context.Context, argv []string) {
checkArgs(argv, 3)
topic := argv[1]
workers, err := strconv.Atoi(argv[2])
if err != nil {
log.Fatalf("Atoi failed, %v", err)
}
result := make(chan int, 1024)
for i := 0; i < int(workers); i++ {
go publishLoop(ctx, topic, i, result)
}
if *reportMPS {
r := reporter{reportTitle: "Sent", result: result}
r.report()
} else {
select {}
}
}
// This example demonstrates calling the Cloud Pub/Sub API. As of 22
// Oct 2014, the Cloud Pub/Sub API is only available if you're
// whitelisted. If you're interested in using it, please apply for the
// Limited Preview program at the following form:
// http://goo.gl/Wql9HL
//
// Also, before running this example, be sure to enable Cloud Pub/Sub
// service on your project in Developer Console at:
// https://console.developers.google.com/
//
// Unless you run this sample on Compute Engine instance, please
// create a new service account and download a JSON key file for it at
// the developer console: https://console.developers.google.com/
//
// It has the following subcommands:
//
// create_topic <name>
// delete_topic <name>
// create_subscription <name> <linked_topic>
// delete_subscription <name>
// publish <topic> <message>
// pull_messages <subscription> <numworkers>
// publish_messages <topic> <numworkers>
//
// You can choose any names for topic and subscription as long as they
// follow the naming rule described at:
// https://cloud.google.com/pubsub/overview#names
//
// You can create/delete topics/subscriptions by self-explanatory
// subcommands.
//
// The "publish" subcommand is for publishing a single message to a
// specified Cloud Pub/Sub topic.
//
// The "pull_messages" subcommand is for continuously pulling messages
// from a specified Cloud Pub/Sub subscription with specified number
// of workers.
//
// The "publish_messages" subcommand is for continuously publishing
// messages to a specified Cloud Pub/Sub topic with specified number
// of workers.
func main() {
flag.Parse()
argv := flag.Args()
checkArgs(argv, 1)
client, err := newClient(*jsonFile)
if err != nil {
log.Fatalf("clientAndId failed, %v", err)
}
if *projID == "" {
usageAndExit("Please specify Project ID.")
}
ctx := cloud.NewContext(*projID, client)
m := map[string]func(ctx context.Context, argv []string){
"create_topic": createTopic,
"delete_topic": deleteTopic,
"create_subscription": createSubscription,
"delete_subscription": deleteSubscription,
"publish": publish,
"pull_messages": pullMessages,
"publish_messages": publishMessages,
}
subcommand := argv[0]
f, ok := m[subcommand]
if !ok {
usageAndExit(fmt.Sprintf("Function not found for %s", subcommand))
}
f(ctx, argv)
}

View file

@ -0,0 +1,401 @@
// Copyright 2014 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 gcsdemo is an example App Engine app using the Google Cloud Storage API.
package gcsdemo
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/appengine"
"google.golang.org/appengine/file"
"google.golang.org/appengine/log"
"google.golang.org/appengine/urlfetch"
"google.golang.org/cloud"
"google.golang.org/cloud/storage"
)
// bucket is a local cache of the app's default bucket name.
var bucket string // or: var bucket = "<your-app-id>.appspot.com"
func init() {
http.HandleFunc("/", handler)
}
// demo struct holds information needed to run the various demo functions.
type demo struct {
c context.Context
w http.ResponseWriter
ctx context.Context
// cleanUp is a list of filenames that need cleaning up at the end of the demo.
cleanUp []string
// failed indicates that one or more of the demo steps failed.
failed bool
}
func (d *demo) errorf(format string, args ...interface{}) {
d.failed = true
log.Errorf(d.c, format, args...)
}
// handler is the main demo entry point that calls the GCS operations.
func handler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
c := appengine.NewContext(r)
if bucket == "" {
var err error
if bucket, err = file.DefaultBucketName(c); err != nil {
log.Errorf(c, "failed to get default GCS bucket name: %v", err)
return
}
}
hc := &http.Client{
Transport: &oauth2.Transport{
Source: google.AppEngineTokenSource(c, storage.ScopeFullControl),
// Note that the App Engine urlfetch service has a limit of 10MB uploads and
// 32MB downloads.
// See https://cloud.google.com/appengine/docs/go/urlfetch/#Go_Quotas_and_limits
// for more information.
Base: &urlfetch.Transport{Context: c},
},
}
ctx := cloud.NewContext(appengine.AppID(c), hc)
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "Demo GCS Application running from Version: %v\n", appengine.VersionID(c))
fmt.Fprintf(w, "Using bucket name: %v\n\n", bucket)
d := &demo{
c: c,
w: w,
ctx: ctx,
}
n := "demo-testfile-go"
d.createFile(n)
d.readFile(n)
d.copyFile(n)
d.statFile(n)
d.createListFiles()
d.listBucket()
d.listBucketDirMode()
d.defaultACL()
d.putDefaultACLRule()
d.deleteDefaultACLRule()
d.bucketACL()
d.putBucketACLRule()
d.deleteBucketACLRule()
d.acl(n)
d.putACLRule(n)
d.deleteACLRule(n)
d.deleteFiles()
if d.failed {
io.WriteString(w, "\nDemo failed.\n")
} else {
io.WriteString(w, "\nDemo succeeded.\n")
}
}
// createFile creates a file in Google Cloud Storage.
func (d *demo) createFile(fileName string) {
fmt.Fprintf(d.w, "Creating file /%v/%v\n", bucket, fileName)
wc := storage.NewWriter(d.ctx, bucket, fileName)
wc.ContentType = "text/plain"
wc.Metadata = map[string]string{
"x-goog-meta-foo": "foo",
"x-goog-meta-bar": "bar",
}
d.cleanUp = append(d.cleanUp, fileName)
if _, err := wc.Write([]byte("abcde\n")); err != nil {
d.errorf("createFile: unable to write data to bucket %q, file %q: %v", bucket, fileName, err)
return
}
if _, err := wc.Write([]byte(strings.Repeat("f", 1024*4) + "\n")); err != nil {
d.errorf("createFile: unable to write data to bucket %q, file %q: %v", bucket, fileName, err)
return
}
if err := wc.Close(); err != nil {
d.errorf("createFile: unable to close bucket %q, file %q: %v", bucket, fileName, err)
return
}
}
// readFile reads the named file in Google Cloud Storage.
func (d *demo) readFile(fileName string) {
io.WriteString(d.w, "\nAbbreviated file content (first line and last 1K):\n")
rc, err := storage.NewReader(d.ctx, bucket, fileName)
if err != nil {
d.errorf("readFile: unable to open file from bucket %q, file %q: %v", bucket, fileName, err)
return
}
defer rc.Close()
slurp, err := ioutil.ReadAll(rc)
if err != nil {
d.errorf("readFile: unable to read data from bucket %q, file %q: %v", bucket, fileName, err)
return
}
fmt.Fprintf(d.w, "%s\n", bytes.SplitN(slurp, []byte("\n"), 2)[0])
if len(slurp) > 1024 {
fmt.Fprintf(d.w, "...%s\n", slurp[len(slurp)-1024:])
} else {
fmt.Fprintf(d.w, "%s\n", slurp)
}
}
// copyFile copies a file in Google Cloud Storage.
func (d *demo) copyFile(fileName string) {
copyName := fileName + "-copy"
fmt.Fprintf(d.w, "Copying file /%v/%v to /%v/%v:\n", bucket, fileName, bucket, copyName)
obj, err := storage.CopyObject(d.ctx, bucket, fileName, bucket, copyName, nil)
if err != nil {
d.errorf("copyFile: unable to copy /%v/%v to bucket %q, file %q: %v", bucket, fileName, bucket, copyName, err)
return
}
d.cleanUp = append(d.cleanUp, copyName)
d.dumpStats(obj)
}
func (d *demo) dumpStats(obj *storage.Object) {
fmt.Fprintf(d.w, "(filename: /%v/%v, ", obj.Bucket, obj.Name)
fmt.Fprintf(d.w, "ContentType: %q, ", obj.ContentType)
fmt.Fprintf(d.w, "ACL: %#v, ", obj.ACL)
fmt.Fprintf(d.w, "Owner: %v, ", obj.Owner)
fmt.Fprintf(d.w, "ContentEncoding: %q, ", obj.ContentEncoding)
fmt.Fprintf(d.w, "Size: %v, ", obj.Size)
fmt.Fprintf(d.w, "MD5: %q, ", obj.MD5)
fmt.Fprintf(d.w, "CRC32C: %q, ", obj.CRC32C)
fmt.Fprintf(d.w, "Metadata: %#v, ", obj.Metadata)
fmt.Fprintf(d.w, "MediaLink: %q, ", obj.MediaLink)
fmt.Fprintf(d.w, "StorageClass: %q, ", obj.StorageClass)
if !obj.Deleted.IsZero() {
fmt.Fprintf(d.w, "Deleted: %v, ", obj.Deleted)
}
fmt.Fprintf(d.w, "Updated: %v)\n", obj.Updated)
}
// statFile reads the stats of the named file in Google Cloud Storage.
func (d *demo) statFile(fileName string) {
io.WriteString(d.w, "\nFile stat:\n")
obj, err := storage.StatObject(d.ctx, bucket, fileName)
if err != nil {
d.errorf("statFile: unable to stat file from bucket %q, file %q: %v", bucket, fileName, err)
return
}
d.dumpStats(obj)
}
// createListFiles creates files that will be used by listBucket.
func (d *demo) createListFiles() {
io.WriteString(d.w, "\nCreating more files for listbucket...\n")
for _, n := range []string{"foo1", "foo2", "bar", "bar/1", "bar/2", "boo/"} {
d.createFile(n)
}
}
// listBucket lists the contents of a bucket in Google Cloud Storage.
func (d *demo) listBucket() {
io.WriteString(d.w, "\nListbucket result:\n")
query := &storage.Query{Prefix: "foo"}
for query != nil {
objs, err := storage.ListObjects(d.ctx, bucket, query)
if err != nil {
d.errorf("listBucket: unable to list bucket %q: %v", bucket, err)
return
}
query = objs.Next
for _, obj := range objs.Results {
d.dumpStats(obj)
}
}
}
func (d *demo) listDir(name, indent string) {
query := &storage.Query{Prefix: name, Delimiter: "/"}
for query != nil {
objs, err := storage.ListObjects(d.ctx, bucket, query)
if err != nil {
d.errorf("listBucketDirMode: unable to list bucket %q: %v", bucket, err)
return
}
query = objs.Next
for _, obj := range objs.Results {
fmt.Fprint(d.w, indent)
d.dumpStats(obj)
}
for _, dir := range objs.Prefixes {
fmt.Fprintf(d.w, "%v(directory: /%v/%v)\n", indent, bucket, dir)
d.listDir(dir, indent+" ")
}
}
}
// listBucketDirMode lists the contents of a bucket in dir mode in Google Cloud Storage.
func (d *demo) listBucketDirMode() {
io.WriteString(d.w, "\nListbucket directory mode result:\n")
d.listDir("b", "")
}
// dumpDefaultACL prints out the default object ACL for this bucket.
func (d *demo) dumpDefaultACL() {
acl, err := storage.DefaultACL(d.ctx, bucket)
if err != nil {
d.errorf("defaultACL: unable to list default object ACL for bucket %q: %v", bucket, err)
return
}
for _, v := range acl {
fmt.Fprintf(d.w, "Entity: %q, Role: %q\n", v.Entity, v.Role)
}
}
// defaultACL displays the default object ACL for this bucket.
func (d *demo) defaultACL() {
io.WriteString(d.w, "\nDefault object ACL:\n")
d.dumpDefaultACL()
}
// putDefaultACLRule adds the "allUsers" default object ACL rule for this bucket.
func (d *demo) putDefaultACLRule() {
io.WriteString(d.w, "\nPut Default object ACL Rule:\n")
err := storage.PutDefaultACLRule(d.ctx, bucket, "allUsers", storage.RoleReader)
if err != nil {
d.errorf("putDefaultACLRule: unable to save default object ACL rule for bucket %q: %v", bucket, err)
return
}
d.dumpDefaultACL()
}
// deleteDefaultACLRule deleted the "allUsers" default object ACL rule for this bucket.
func (d *demo) deleteDefaultACLRule() {
io.WriteString(d.w, "\nDelete Default object ACL Rule:\n")
err := storage.DeleteDefaultACLRule(d.ctx, bucket, "allUsers")
if err != nil {
d.errorf("deleteDefaultACLRule: unable to delete default object ACL rule for bucket %q: %v", bucket, err)
return
}
d.dumpDefaultACL()
}
// dumpBucketACL prints out the bucket ACL.
func (d *demo) dumpBucketACL() {
acl, err := storage.BucketACL(d.ctx, bucket)
if err != nil {
d.errorf("dumpBucketACL: unable to list bucket ACL for bucket %q: %v", bucket, err)
return
}
for _, v := range acl {
fmt.Fprintf(d.w, "Entity: %q, Role: %q\n", v.Entity, v.Role)
}
}
// bucketACL displays the bucket ACL for this bucket.
func (d *demo) bucketACL() {
io.WriteString(d.w, "\nBucket ACL:\n")
d.dumpBucketACL()
}
// putBucketACLRule adds the "allUsers" bucket ACL rule for this bucket.
func (d *demo) putBucketACLRule() {
io.WriteString(d.w, "\nPut Bucket ACL Rule:\n")
err := storage.PutBucketACLRule(d.ctx, bucket, "allUsers", storage.RoleReader)
if err != nil {
d.errorf("putBucketACLRule: unable to save bucket ACL rule for bucket %q: %v", bucket, err)
return
}
d.dumpBucketACL()
}
// deleteBucketACLRule deleted the "allUsers" bucket ACL rule for this bucket.
func (d *demo) deleteBucketACLRule() {
io.WriteString(d.w, "\nDelete Bucket ACL Rule:\n")
err := storage.DeleteBucketACLRule(d.ctx, bucket, "allUsers")
if err != nil {
d.errorf("deleteBucketACLRule: unable to delete bucket ACL rule for bucket %q: %v", bucket, err)
return
}
d.dumpBucketACL()
}
// dumpACL prints out the ACL of the named file.
func (d *demo) dumpACL(fileName string) {
acl, err := storage.ACL(d.ctx, bucket, fileName)
if err != nil {
d.errorf("dumpACL: unable to list file ACL for bucket %q, file %q: %v", bucket, fileName, err)
return
}
for _, v := range acl {
fmt.Fprintf(d.w, "Entity: %q, Role: %q\n", v.Entity, v.Role)
}
}
// acl displays the ACL for the named file.
func (d *demo) acl(fileName string) {
fmt.Fprintf(d.w, "\nACL for file %v:\n", fileName)
d.dumpACL(fileName)
}
// putACLRule adds the "allUsers" ACL rule for the named file.
func (d *demo) putACLRule(fileName string) {
fmt.Fprintf(d.w, "\nPut ACL rule for file %v:\n", fileName)
err := storage.PutACLRule(d.ctx, bucket, fileName, "allUsers", storage.RoleReader)
if err != nil {
d.errorf("putACLRule: unable to save ACL rule for bucket %q, file %q: %v", bucket, fileName, err)
return
}
d.dumpACL(fileName)
}
// deleteACLRule deleted the "allUsers" ACL rule for the named file.
func (d *demo) deleteACLRule(fileName string) {
fmt.Fprintf(d.w, "\nDelete ACL rule for file %v:\n", fileName)
err := storage.DeleteACLRule(d.ctx, bucket, fileName, "allUsers")
if err != nil {
d.errorf("deleteACLRule: unable to delete ACL rule for bucket %q, file %q: %v", bucket, fileName, err)
return
}
d.dumpACL(fileName)
}
// deleteFiles deletes all the temporary files from a bucket created by this demo.
func (d *demo) deleteFiles() {
io.WriteString(d.w, "\nDeleting files...\n")
for _, v := range d.cleanUp {
fmt.Fprintf(d.w, "Deleting file %v\n", v)
if err := storage.DeleteObject(d.ctx, bucket, v); err != nil {
d.errorf("deleteFiles: unable to delete bucket %q, file %q: %v", bucket, v, err)
return
}
}
}

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