307 lines
10 KiB
Go
307 lines
10 KiB
Go
/*
|
|
Copyright 2017 Google Inc. All Rights Reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package spanner
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
proto3 "github.com/golang/protobuf/ptypes/struct"
|
|
|
|
sppb "google.golang.org/genproto/googleapis/spanner/v1"
|
|
"google.golang.org/grpc/codes"
|
|
)
|
|
|
|
// A Row is a view of a row of data produced by a Cloud Spanner read.
|
|
//
|
|
// A row consists of a number of columns; the number depends on the columns
|
|
// used to construct the read.
|
|
//
|
|
// The column values can be accessed by index, where the indices are with
|
|
// respect to the columns. For instance, if the read specified
|
|
// []string{"photo_id", "caption", "metadata"}, then each row will
|
|
// contain three columns: the 0th column corresponds to "photo_id", the
|
|
// 1st column corresponds to "caption", etc.
|
|
//
|
|
// Column values are decoded by using one of the Column, ColumnByName, or
|
|
// Columns methods. The valid values passed to these methods depend on the
|
|
// column type. For example:
|
|
//
|
|
// var photoID int64
|
|
// err := row.Column(0, &photoID) // Decode column 0 as an integer.
|
|
//
|
|
// var caption string
|
|
// err := row.Column(1, &caption) // Decode column 1 as a string.
|
|
//
|
|
// // The above two operations at once.
|
|
// err := row.Columns(&photoID, &caption)
|
|
//
|
|
// Supported types and their corresponding Cloud Spanner column type(s) are:
|
|
//
|
|
// *string(not NULL), *NullString - STRING
|
|
// *[]NullString - STRING ARRAY
|
|
// *[]byte - BYTES
|
|
// *[][]byte - BYTES ARRAY
|
|
// *int64(not NULL), *NullInt64 - INT64
|
|
// *[]NullInt64 - INT64 ARRAY
|
|
// *bool(not NULL), *NullBool - BOOL
|
|
// *[]NullBool - BOOL ARRAY
|
|
// *float64(not NULL), *NullFloat64 - FLOAT64
|
|
// *[]NullFloat64 - FLOAT64 ARRAY
|
|
// *time.Time(not NULL), *NullTime - TIMESTAMP
|
|
// *[]NullTime - TIMESTAMP ARRAY
|
|
// *Date(not NULL), *NullDate - DATE
|
|
// *[]NullDate - DATE ARRAY
|
|
// *[]*some_go_struct, *[]NullRow - STRUCT ARRAY
|
|
// *GenericColumnValue - any Cloud Spanner type
|
|
//
|
|
// For TIMESTAMP columns, returned time.Time object will be in UTC.
|
|
//
|
|
// To fetch an array of BYTES, pass a *[][]byte. To fetch an array of
|
|
// (sub)rows, pass a *[]spanner.NullRow or a *[]*some_go_struct where
|
|
// some_go_struct holds all information of the subrow, see spannr.Row.ToStruct
|
|
// for the mapping between Cloud Spanner row and Go struct. To fetch an array of
|
|
// other types, pass a *[]spanner.Null* type of the appropriate type. Use
|
|
// *GenericColumnValue when you don't know in advance what column type to
|
|
// expect.
|
|
//
|
|
// Row decodes the row contents lazily; as a result, each call to a getter has
|
|
// a chance of returning an error.
|
|
//
|
|
// A column value may be NULL if the corresponding value is not present in
|
|
// Cloud Spanner. The spanner.Null* types (spanner.NullInt64 et al.) allow fetching
|
|
// values that may be null. A NULL BYTES can be fetched into a *[]byte as nil.
|
|
// It is an error to fetch a NULL value into any other type.
|
|
type Row struct {
|
|
fields []*sppb.StructType_Field
|
|
vals []*proto3.Value // keep decoded for now
|
|
}
|
|
|
|
// errNamesValuesMismatch returns error for when columnNames count is not equal
|
|
// to columnValues count.
|
|
func errNamesValuesMismatch(columnNames []string, columnValues []interface{}) error {
|
|
return spannerErrorf(codes.FailedPrecondition,
|
|
"different number of names(%v) and values(%v)", len(columnNames), len(columnValues))
|
|
}
|
|
|
|
// NewRow returns a Row containing the supplied data. This can be useful for
|
|
// mocking Cloud Spanner Read and Query responses for unit testing.
|
|
func NewRow(columnNames []string, columnValues []interface{}) (*Row, error) {
|
|
if len(columnValues) != len(columnNames) {
|
|
return nil, errNamesValuesMismatch(columnNames, columnValues)
|
|
}
|
|
r := Row{
|
|
fields: make([]*sppb.StructType_Field, len(columnValues)),
|
|
vals: make([]*proto3.Value, len(columnValues)),
|
|
}
|
|
for i := range columnValues {
|
|
val, typ, err := encodeValue(columnValues[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r.fields[i] = &sppb.StructType_Field{
|
|
Name: columnNames[i],
|
|
Type: typ,
|
|
}
|
|
r.vals[i] = val
|
|
}
|
|
return &r, nil
|
|
}
|
|
|
|
// Size is the number of columns in the row.
|
|
func (r *Row) Size() int {
|
|
return len(r.fields)
|
|
}
|
|
|
|
// ColumnName returns the name of column i, or empty string for invalid column.
|
|
func (r *Row) ColumnName(i int) string {
|
|
if i < 0 || i >= len(r.fields) {
|
|
return ""
|
|
}
|
|
return r.fields[i].Name
|
|
}
|
|
|
|
// ColumnIndex returns the index of the column with the given name. The
|
|
// comparison is case-sensitive.
|
|
func (r *Row) ColumnIndex(name string) (int, error) {
|
|
found := false
|
|
var index int
|
|
if len(r.vals) != len(r.fields) {
|
|
return 0, errFieldsMismatchVals(r)
|
|
}
|
|
for i, f := range r.fields {
|
|
if f == nil {
|
|
return 0, errNilColType(i)
|
|
}
|
|
if name == f.Name {
|
|
if found {
|
|
return 0, errDupColName(name)
|
|
}
|
|
found = true
|
|
index = i
|
|
}
|
|
}
|
|
if !found {
|
|
return 0, errColNotFound(name)
|
|
}
|
|
return index, nil
|
|
}
|
|
|
|
// ColumnNames returns all column names of the row.
|
|
func (r *Row) ColumnNames() []string {
|
|
var n []string
|
|
for _, c := range r.fields {
|
|
n = append(n, c.Name)
|
|
}
|
|
return n
|
|
}
|
|
|
|
// errColIdxOutOfRange returns error for requested column index is out of the
|
|
// range of the target Row's columns.
|
|
func errColIdxOutOfRange(i int, r *Row) error {
|
|
return spannerErrorf(codes.OutOfRange, "column index %d out of range [0,%d)", i, len(r.vals))
|
|
}
|
|
|
|
// errDecodeColumn returns error for not being able to decode a indexed column.
|
|
func errDecodeColumn(i int, err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
se, ok := toSpannerError(err).(*Error)
|
|
if !ok {
|
|
return spannerErrorf(codes.InvalidArgument, "failed to decode column %v, error = <%v>", i, err)
|
|
}
|
|
se.decorate(fmt.Sprintf("failed to decode column %v", i))
|
|
return se
|
|
}
|
|
|
|
// errFieldsMismatchVals returns error for field count isn't equal to value count in a Row.
|
|
func errFieldsMismatchVals(r *Row) error {
|
|
return spannerErrorf(codes.FailedPrecondition, "row has different number of fields(%v) and values(%v)",
|
|
len(r.fields), len(r.vals))
|
|
}
|
|
|
|
// errNilColType returns error for column type for column i being nil in the row.
|
|
func errNilColType(i int) error {
|
|
return spannerErrorf(codes.FailedPrecondition, "column(%v)'s type is nil", i)
|
|
}
|
|
|
|
// Column fetches the value from the ith column, decoding it into ptr.
|
|
// See the Row documentation for the list of acceptable argument types.
|
|
func (r *Row) Column(i int, ptr interface{}) error {
|
|
if len(r.vals) != len(r.fields) {
|
|
return errFieldsMismatchVals(r)
|
|
}
|
|
if i < 0 || i >= len(r.fields) {
|
|
return errColIdxOutOfRange(i, r)
|
|
}
|
|
if r.fields[i] == nil {
|
|
return errNilColType(i)
|
|
}
|
|
if err := decodeValue(r.vals[i], r.fields[i].Type, ptr); err != nil {
|
|
return errDecodeColumn(i, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// errDupColName returns error for duplicated column name in the same row.
|
|
func errDupColName(n string) error {
|
|
return spannerErrorf(codes.FailedPrecondition, "ambiguous column name %q", n)
|
|
}
|
|
|
|
// errColNotFound returns error for not being able to find a named column.
|
|
func errColNotFound(n string) error {
|
|
return spannerErrorf(codes.NotFound, "column %q not found", n)
|
|
}
|
|
|
|
// ColumnByName fetches the value from the named column, decoding it into ptr.
|
|
// See the Row documentation for the list of acceptable argument types.
|
|
func (r *Row) ColumnByName(name string, ptr interface{}) error {
|
|
index, err := r.ColumnIndex(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return r.Column(index, ptr)
|
|
}
|
|
|
|
// errNumOfColValue returns error for providing wrong number of values to Columns.
|
|
func errNumOfColValue(n int, r *Row) error {
|
|
return spannerErrorf(codes.InvalidArgument,
|
|
"Columns(): number of arguments (%d) does not match row size (%d)", n, len(r.vals))
|
|
}
|
|
|
|
// Columns fetches all the columns in the row at once.
|
|
//
|
|
// The value of the kth column will be decoded into the kth argument to
|
|
// Columns. See above for the list of acceptable argument types. The number of
|
|
// arguments must be equal to the number of columns. Pass nil to specify that a
|
|
// column should be ignored.
|
|
func (r *Row) Columns(ptrs ...interface{}) error {
|
|
if len(ptrs) != len(r.vals) {
|
|
return errNumOfColValue(len(ptrs), r)
|
|
}
|
|
if len(r.vals) != len(r.fields) {
|
|
return errFieldsMismatchVals(r)
|
|
}
|
|
for i, p := range ptrs {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
if err := r.Column(i, p); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// errToStructArgType returns error for p not having the correct data type(pointer to Go struct) to
|
|
// be the argument of Row.ToStruct.
|
|
func errToStructArgType(p interface{}) error {
|
|
return spannerErrorf(codes.InvalidArgument, "ToStruct(): type %T is not a valid pointer to Go struct", p)
|
|
}
|
|
|
|
// ToStruct fetches the columns in a row into the fields of a struct.
|
|
// The rules for mapping a row's columns into a struct's exported fields
|
|
// are as the following:
|
|
// 1. If a field has a `spanner: "column_name"` tag, then decode column
|
|
// 'column_name' into the field. A special case is the `spanner: "-"`
|
|
// tag, which instructs ToStruct to ignore the field during decoding.
|
|
// 2. Otherwise, if the name of a field matches the name of a column (ignoring case),
|
|
// decode the column into the field.
|
|
//
|
|
// The fields of the destination struct can be of any type that is acceptable
|
|
// to (*spanner.Row).Column.
|
|
//
|
|
// Slice and pointer fields will be set to nil if the source column
|
|
// is NULL, and a non-nil value if the column is not NULL. To decode NULL
|
|
// values of other types, use one of the spanner.Null* as the type of the
|
|
// destination field.
|
|
func (r *Row) ToStruct(p interface{}) error {
|
|
// Check if p is a pointer to a struct
|
|
if t := reflect.TypeOf(p); t == nil || t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
|
|
return errToStructArgType(p)
|
|
}
|
|
if len(r.vals) != len(r.fields) {
|
|
return errFieldsMismatchVals(r)
|
|
}
|
|
// Call decodeStruct directly to decode the row as a typed proto.ListValue.
|
|
return decodeStruct(
|
|
&sppb.StructType{Fields: r.fields},
|
|
&proto3.ListValue{Values: r.vals},
|
|
p,
|
|
)
|
|
}
|