| /* | |
| 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 ( | |
| "fmt" | |
| "reflect" | |
| sppb "cloud.google.com/go/spanner/apiv1/spannerpb" | |
| proto3 "github.com/golang/protobuf/ptypes/struct" | |
| "google.golang.org/grpc/codes" | |
| ) | |
| // A Row is a view of a row of data returned by a Cloud Spanner read. | |
| // It 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. For instance, if the read specified | |
| // []string{"photo_id", "caption"}, then each row will contain two | |
| // columns: "photo_id" with index 0, and "caption" with index 1. | |
| // | |
| // 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. | |
| // | |
| // // Decode all the columns. | |
| // err := row.Columns(&photoID, &caption) | |
| // | |
| // Supported types and their corresponding Cloud Spanner column type(s) are: | |
| // | |
| // *string(not NULL), *NullString - STRING | |
| // *[]string, *[]NullString - STRING ARRAY | |
| // *[]byte - BYTES | |
| // *[][]byte - BYTES ARRAY | |
| // *int64(not NULL), *NullInt64 - INT64 | |
| // *[]int64, *[]NullInt64 - INT64 ARRAY | |
| // *bool(not NULL), *NullBool - BOOL | |
| // *[]bool, *[]NullBool - BOOL ARRAY | |
| // *float64(not NULL), *NullFloat64 - FLOAT64 | |
| // *[]float64, *[]NullFloat64 - FLOAT64 ARRAY | |
| // *big.Rat(not NULL), *NullNumeric - NUMERIC | |
| // *[]big.Rat, *[]NullNumeric - NUMERIC ARRAY | |
| // *time.Time(not NULL), *NullTime - TIMESTAMP | |
| // *[]time.Time, *[]NullTime - TIMESTAMP ARRAY | |
| // *Date(not NULL), *NullDate - DATE | |
| // *[]civil.Date, *[]NullDate - DATE ARRAY | |
| // *[]*some_go_struct, *[]NullRow - STRUCT ARRAY | |
| // *NullJSON - JSON | |
| // *[]NullJSON - JSON ARRAY | |
| // *GenericColumnValue - any Cloud Spanner type | |
| // | |
| // For TIMESTAMP columns, the 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 spanner.Row.ToStruct for the mapping between a | |
| // Cloud Spanner row and a Go struct. To fetch an array of other types, pass a | |
| // *[]spanner.NullXXX 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.NullXXX 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 | |
| } | |
| // String implements fmt.stringer. | |
| func (r *Row) String() string { | |
| return fmt.Sprintf("{fields: %s, values: %s}", r.fields, r.vals) | |
| } | |
| // 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 | |
| } | |
| var se *Error | |
| if !errorAs(err, &se) { | |
| 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. | |
| // see Client.ReadWriteTransaction for an example. | |
| 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 | |
| // Row 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: | |
| // | |
| // 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. | |
| // | |
| // 3. The number of columns in the row must match the number of exported fields in the struct. | |
| // There must be exactly one match for each column in the row. The method will return an error | |
| // if a column in the row cannot be assigned to a field in the struct. | |
| // | |
| // 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.NullXXX types as the type of the destination field. | |
| // | |
| // If ToStruct returns an error, the contents of p are undefined. Some fields may | |
| // have been successfully populated, while others were not; you should not use any of | |
| // the fields. | |
| 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, | |
| false, | |
| ) | |
| } | |
| // ToStructLenient 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: | |
| // | |
| // 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. | |
| // | |
| // 3. The number of columns in the row and exported fields in the struct do not need to match. | |
| // Any field in the struct that cannot not be assigned a value from the row is assigned its default value. | |
| // Any column in the row that does not have a corresponding field in the struct is ignored. | |
| // | |
| // 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.NullXXX types as the type of the destination field. | |
| // | |
| // If ToStructLenient returns an error, the contents of p are undefined. Some fields may | |
| // have been successfully populated, while others were not; you should not use any of | |
| // the fields. | |
| func (r *Row) ToStructLenient(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, | |
| true, | |
| ) | |
| } |