4e7ce6f42b
* Update deps * Change azure dep to match plugin
431 lines
14 KiB
Go
431 lines
14 KiB
Go
/*
|
|
Copyright 2017 Google LLC
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
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 spanner
|
|
|
|
import (
|
|
"reflect"
|
|
|
|
proto3 "github.com/golang/protobuf/ptypes/struct"
|
|
sppb "google.golang.org/genproto/googleapis/spanner/v1"
|
|
"google.golang.org/grpc/codes"
|
|
)
|
|
|
|
// op is the mutation operation.
|
|
type op int
|
|
|
|
const (
|
|
// opDelete removes a row from a table. Succeeds whether or not the
|
|
// key was present.
|
|
opDelete op = iota
|
|
// opInsert inserts a row into a table. If the row already exists, the
|
|
// write or transaction fails.
|
|
opInsert
|
|
// opInsertOrUpdate inserts a row into a table. If the row already
|
|
// exists, it updates it instead. Any column values not explicitly
|
|
// written are preserved.
|
|
opInsertOrUpdate
|
|
// opReplace inserts a row into a table, deleting any existing row.
|
|
// Unlike InsertOrUpdate, this means any values not explicitly written
|
|
// become NULL.
|
|
opReplace
|
|
// opUpdate updates a row in a table. If the row does not already
|
|
// exist, the write or transaction fails.
|
|
opUpdate
|
|
)
|
|
|
|
// A Mutation describes a modification to one or more Cloud Spanner rows. The
|
|
// mutation represents an insert, update, delete, etc on a table.
|
|
//
|
|
// Many mutations can be applied in a single atomic commit. For purposes of
|
|
// constraint checking (such as foreign key constraints), the operations can be
|
|
// viewed as applying in the same order as the mutations are provided (so that, e.g.,
|
|
// a row and its logical "child" can be inserted in the same commit).
|
|
//
|
|
// The Apply function applies series of mutations. For example,
|
|
//
|
|
// m := spanner.Insert("User",
|
|
// []string{"user_id", "profile"},
|
|
// []interface{}{UserID, profile})
|
|
// _, err := client.Apply(ctx, []*spanner.Mutation{m})
|
|
//
|
|
// inserts a new row into the User table. The primary key
|
|
// for the new row is UserID (presuming that "user_id" has been declared as the
|
|
// primary key of the "User" table).
|
|
//
|
|
// To apply a series of mutations as part of an atomic read-modify-write operation,
|
|
// use ReadWriteTransaction.
|
|
//
|
|
// Updating a row
|
|
//
|
|
// Changing the values of columns in an existing row is very similar to
|
|
// inserting a new row:
|
|
//
|
|
// m := spanner.Update("User",
|
|
// []string{"user_id", "profile"},
|
|
// []interface{}{UserID, profile})
|
|
// _, err := client.Apply(ctx, []*spanner.Mutation{m})
|
|
//
|
|
// Deleting a row
|
|
//
|
|
// To delete a row, use spanner.Delete:
|
|
//
|
|
// m := spanner.Delete("User", spanner.Key{UserId})
|
|
// _, err := client.Apply(ctx, []*spanner.Mutation{m})
|
|
//
|
|
// spanner.Delete accepts a KeySet, so you can also pass in a KeyRange, or use the
|
|
// spanner.KeySets function to build any combination of Keys and KeyRanges.
|
|
//
|
|
// Note that deleting a row in a table may also delete rows from other tables
|
|
// if cascading deletes are specified in those tables' schemas. Delete does
|
|
// nothing if the named row does not exist (does not yield an error).
|
|
//
|
|
// Deleting a field
|
|
//
|
|
// To delete/clear a field within a row, use spanner.Update with the value nil:
|
|
//
|
|
// m := spanner.Update("User",
|
|
// []string{"user_id", "profile"},
|
|
// []interface{}{UserID, nil})
|
|
// _, err := client.Apply(ctx, []*spanner.Mutation{m})
|
|
//
|
|
// The valid Go types and their corresponding Cloud Spanner types that can be
|
|
// used in the Insert/Update/InsertOrUpdate functions are:
|
|
//
|
|
// string, NullString - STRING
|
|
// []string, []NullString - STRING ARRAY
|
|
// []byte - BYTES
|
|
// [][]byte - BYTES ARRAY
|
|
// int, int64, NullInt64 - INT64
|
|
// []int, []int64, []NullInt64 - INT64 ARRAY
|
|
// bool, NullBool - BOOL
|
|
// []bool, []NullBool - BOOL ARRAY
|
|
// float64, NullFloat64 - FLOAT64
|
|
// []float64, []NullFloat64 - FLOAT64 ARRAY
|
|
// time.Time, NullTime - TIMESTAMP
|
|
// []time.Time, []NullTime - TIMESTAMP ARRAY
|
|
// Date, NullDate - DATE
|
|
// []Date, []NullDate - DATE ARRAY
|
|
//
|
|
// To compare two Mutations for testing purposes, use reflect.DeepEqual.
|
|
type Mutation struct {
|
|
// op is the operation type of the mutation.
|
|
// See documentation for spanner.op for more details.
|
|
op op
|
|
// Table is the name of the target table to be modified.
|
|
table string
|
|
// keySet is a set of primary keys that names the rows
|
|
// in a delete operation.
|
|
keySet KeySet
|
|
// columns names the set of columns that are going to be
|
|
// modified by Insert, InsertOrUpdate, Replace or Update
|
|
// operations.
|
|
columns []string
|
|
// values specifies the new values for the target columns
|
|
// named by Columns.
|
|
values []interface{}
|
|
}
|
|
|
|
// mapToMutationParams converts Go map into mutation parameters.
|
|
func mapToMutationParams(in map[string]interface{}) ([]string, []interface{}) {
|
|
cols := []string{}
|
|
vals := []interface{}{}
|
|
for k, v := range in {
|
|
cols = append(cols, k)
|
|
vals = append(vals, v)
|
|
}
|
|
return cols, vals
|
|
}
|
|
|
|
// errNotStruct returns error for not getting a go struct type.
|
|
func errNotStruct(in interface{}) error {
|
|
return spannerErrorf(codes.InvalidArgument, "%T is not a go struct type", in)
|
|
}
|
|
|
|
// structToMutationParams converts Go struct into mutation parameters.
|
|
// If the input is not a valid Go struct type, structToMutationParams
|
|
// returns error.
|
|
func structToMutationParams(in interface{}) ([]string, []interface{}, error) {
|
|
if in == nil {
|
|
return nil, nil, errNotStruct(in)
|
|
}
|
|
v := reflect.ValueOf(in)
|
|
t := v.Type()
|
|
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
|
|
// t is a pointer to a struct.
|
|
if v.IsNil() {
|
|
// Return empty results.
|
|
return nil, nil, nil
|
|
}
|
|
// Get the struct value that in points to.
|
|
v = v.Elem()
|
|
t = t.Elem()
|
|
}
|
|
if t.Kind() != reflect.Struct {
|
|
return nil, nil, errNotStruct(in)
|
|
}
|
|
fields, err := fieldCache.Fields(t)
|
|
if err != nil {
|
|
return nil, nil, toSpannerError(err)
|
|
}
|
|
var cols []string
|
|
var vals []interface{}
|
|
for _, f := range fields {
|
|
cols = append(cols, f.Name)
|
|
vals = append(vals, v.FieldByIndex(f.Index).Interface())
|
|
}
|
|
return cols, vals, nil
|
|
}
|
|
|
|
// Insert returns a Mutation to insert a row into a table. If the row already
|
|
// exists, the write or transaction fails.
|
|
func Insert(table string, cols []string, vals []interface{}) *Mutation {
|
|
return &Mutation{
|
|
op: opInsert,
|
|
table: table,
|
|
columns: cols,
|
|
values: vals,
|
|
}
|
|
}
|
|
|
|
// InsertMap returns a Mutation to insert a row into a table, specified by
|
|
// a map of column name to value. If the row already exists, the write or
|
|
// transaction fails.
|
|
func InsertMap(table string, in map[string]interface{}) *Mutation {
|
|
cols, vals := mapToMutationParams(in)
|
|
return Insert(table, cols, vals)
|
|
}
|
|
|
|
// InsertStruct returns a Mutation to insert a row into a table, specified by
|
|
// a Go struct. If the row already exists, the write or transaction fails.
|
|
//
|
|
// The in argument must be a struct or a pointer to a struct. Its exported
|
|
// fields specify the column names and values. Use a field tag like "spanner:name"
|
|
// to provide an alternative column name, or use "spanner:-" to ignore the field.
|
|
func InsertStruct(table string, in interface{}) (*Mutation, error) {
|
|
cols, vals, err := structToMutationParams(in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Insert(table, cols, vals), nil
|
|
}
|
|
|
|
// Update returns a Mutation to update a row in a table. If the row does not
|
|
// already exist, the write or transaction fails.
|
|
func Update(table string, cols []string, vals []interface{}) *Mutation {
|
|
return &Mutation{
|
|
op: opUpdate,
|
|
table: table,
|
|
columns: cols,
|
|
values: vals,
|
|
}
|
|
}
|
|
|
|
// UpdateMap returns a Mutation to update a row in a table, specified by
|
|
// a map of column to value. If the row does not already exist, the write or
|
|
// transaction fails.
|
|
func UpdateMap(table string, in map[string]interface{}) *Mutation {
|
|
cols, vals := mapToMutationParams(in)
|
|
return Update(table, cols, vals)
|
|
}
|
|
|
|
// UpdateStruct returns a Mutation to update a row in a table, specified by a Go
|
|
// struct. If the row does not already exist, the write or transaction fails.
|
|
func UpdateStruct(table string, in interface{}) (*Mutation, error) {
|
|
cols, vals, err := structToMutationParams(in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Update(table, cols, vals), nil
|
|
}
|
|
|
|
// InsertOrUpdate returns a Mutation to insert a row into a table. If the row
|
|
// already exists, it updates it instead. Any column values not explicitly
|
|
// written are preserved.
|
|
//
|
|
// For a similar example, See Update.
|
|
func InsertOrUpdate(table string, cols []string, vals []interface{}) *Mutation {
|
|
return &Mutation{
|
|
op: opInsertOrUpdate,
|
|
table: table,
|
|
columns: cols,
|
|
values: vals,
|
|
}
|
|
}
|
|
|
|
// InsertOrUpdateMap returns a Mutation to insert a row into a table,
|
|
// specified by a map of column to value. If the row already exists, it
|
|
// updates it instead. Any column values not explicitly written are preserved.
|
|
//
|
|
// For a similar example, See UpdateMap.
|
|
func InsertOrUpdateMap(table string, in map[string]interface{}) *Mutation {
|
|
cols, vals := mapToMutationParams(in)
|
|
return InsertOrUpdate(table, cols, vals)
|
|
}
|
|
|
|
// InsertOrUpdateStruct returns a Mutation to insert a row into a table,
|
|
// specified by a Go struct. If the row already exists, it updates it instead.
|
|
// Any column values not explicitly written are preserved.
|
|
//
|
|
// The in argument must be a struct or a pointer to a struct. Its exported
|
|
// fields specify the column names and values. Use a field tag like "spanner:name"
|
|
// to provide an alternative column name, or use "spanner:-" to ignore the field.
|
|
//
|
|
// For a similar example, See UpdateStruct.
|
|
func InsertOrUpdateStruct(table string, in interface{}) (*Mutation, error) {
|
|
cols, vals, err := structToMutationParams(in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return InsertOrUpdate(table, cols, vals), nil
|
|
}
|
|
|
|
// Replace returns a Mutation to insert a row into a table, deleting any
|
|
// existing row. Unlike InsertOrUpdate, this means any values not explicitly
|
|
// written become NULL.
|
|
//
|
|
// For a similar example, See Update.
|
|
func Replace(table string, cols []string, vals []interface{}) *Mutation {
|
|
return &Mutation{
|
|
op: opReplace,
|
|
table: table,
|
|
columns: cols,
|
|
values: vals,
|
|
}
|
|
}
|
|
|
|
// ReplaceMap returns a Mutation to insert a row into a table, deleting any
|
|
// existing row. Unlike InsertOrUpdateMap, this means any values not explicitly
|
|
// written become NULL. The row is specified by a map of column to value.
|
|
//
|
|
// For a similar example, See UpdateMap.
|
|
func ReplaceMap(table string, in map[string]interface{}) *Mutation {
|
|
cols, vals := mapToMutationParams(in)
|
|
return Replace(table, cols, vals)
|
|
}
|
|
|
|
// ReplaceStruct returns a Mutation to insert a row into a table, deleting any
|
|
// existing row. Unlike InsertOrUpdateMap, this means any values not explicitly
|
|
// written become NULL. The row is specified by a Go struct.
|
|
//
|
|
// The in argument must be a struct or a pointer to a struct. Its exported
|
|
// fields specify the column names and values. Use a field tag like "spanner:name"
|
|
// to provide an alternative column name, or use "spanner:-" to ignore the field.
|
|
//
|
|
// For a similar example, See UpdateStruct.
|
|
func ReplaceStruct(table string, in interface{}) (*Mutation, error) {
|
|
cols, vals, err := structToMutationParams(in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Replace(table, cols, vals), nil
|
|
}
|
|
|
|
// Delete removes the rows described by the KeySet from the table. It succeeds
|
|
// whether or not the keys were present.
|
|
func Delete(table string, ks KeySet) *Mutation {
|
|
return &Mutation{
|
|
op: opDelete,
|
|
table: table,
|
|
keySet: ks,
|
|
}
|
|
}
|
|
|
|
// prepareWrite generates sppb.Mutation_Write from table name, column names
|
|
// and new column values.
|
|
func prepareWrite(table string, columns []string, vals []interface{}) (*sppb.Mutation_Write, error) {
|
|
v, err := encodeValueArray(vals)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &sppb.Mutation_Write{
|
|
Table: table,
|
|
Columns: columns,
|
|
Values: []*proto3.ListValue{v},
|
|
}, nil
|
|
}
|
|
|
|
// errInvdMutationOp returns error for unrecognized mutation operation.
|
|
func errInvdMutationOp(m Mutation) error {
|
|
return spannerErrorf(codes.InvalidArgument, "Unknown op type: %d", m.op)
|
|
}
|
|
|
|
// proto converts spanner.Mutation to sppb.Mutation, in preparation to send
|
|
// RPCs.
|
|
func (m Mutation) proto() (*sppb.Mutation, error) {
|
|
var pb *sppb.Mutation
|
|
switch m.op {
|
|
case opDelete:
|
|
var kp *sppb.KeySet
|
|
if m.keySet != nil {
|
|
var err error
|
|
kp, err = m.keySet.keySetProto()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
pb = &sppb.Mutation{
|
|
Operation: &sppb.Mutation_Delete_{
|
|
Delete: &sppb.Mutation_Delete{
|
|
Table: m.table,
|
|
KeySet: kp,
|
|
},
|
|
},
|
|
}
|
|
case opInsert:
|
|
w, err := prepareWrite(m.table, m.columns, m.values)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pb = &sppb.Mutation{Operation: &sppb.Mutation_Insert{Insert: w}}
|
|
case opInsertOrUpdate:
|
|
w, err := prepareWrite(m.table, m.columns, m.values)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pb = &sppb.Mutation{Operation: &sppb.Mutation_InsertOrUpdate{InsertOrUpdate: w}}
|
|
case opReplace:
|
|
w, err := prepareWrite(m.table, m.columns, m.values)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pb = &sppb.Mutation{Operation: &sppb.Mutation_Replace{Replace: w}}
|
|
case opUpdate:
|
|
w, err := prepareWrite(m.table, m.columns, m.values)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pb = &sppb.Mutation{Operation: &sppb.Mutation_Update{Update: w}}
|
|
default:
|
|
return nil, errInvdMutationOp(m)
|
|
}
|
|
return pb, nil
|
|
}
|
|
|
|
// mutationsProto turns a spanner.Mutation array into a sppb.Mutation array,
|
|
// it is convenient for sending batch mutations to Cloud Spanner.
|
|
func mutationsProto(ms []*Mutation) ([]*sppb.Mutation, error) {
|
|
l := make([]*sppb.Mutation, 0, len(ms))
|
|
for _, m := range ms {
|
|
pb, err := m.proto()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
l = append(l, pb)
|
|
}
|
|
return l, nil
|
|
}
|