|
- /*
- 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
- }
|