| /* |
| 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 ( |
| "encoding/base64" |
| "fmt" |
| "math" |
| "reflect" |
| "strconv" |
| "time" |
| |
| "cloud.google.com/go/civil" |
| "cloud.google.com/go/internal/fields" |
| proto "github.com/golang/protobuf/proto" |
| proto3 "github.com/golang/protobuf/ptypes/struct" |
| sppb "google.golang.org/genproto/googleapis/spanner/v1" |
| "google.golang.org/grpc/codes" |
| ) |
| |
| const commitTimestampPlaceholderString = "spanner.commit_timestamp()" |
| |
| var ( |
| // CommitTimestamp is a special value used to tell Cloud Spanner |
| // to insert the commit timestamp of the transaction into a column. |
| // It can be used in a Mutation, or directly used in |
| // InsertStruct or InsertMap. See ExampleCommitTimestamp. |
| // This is just a placeholder and the actual value stored in this |
| // variable has no meaning. |
| CommitTimestamp = commitTimestamp |
| commitTimestamp = time.Unix(0, 0).In(time.FixedZone("CommitTimestamp placeholder", 0xDB)) |
| ) |
| |
| // NullInt64 represents a Cloud Spanner INT64 that may be NULL. |
| type NullInt64 struct { |
| Int64 int64 |
| Valid bool // Valid is true if Int64 is not NULL. |
| } |
| |
| // String implements Stringer.String for NullInt64 |
| func (n NullInt64) String() string { |
| if !n.Valid { |
| return fmt.Sprintf("%v", "<null>") |
| } |
| return fmt.Sprintf("%v", n.Int64) |
| } |
| |
| // NullString represents a Cloud Spanner STRING that may be NULL. |
| type NullString struct { |
| StringVal string |
| Valid bool // Valid is true if StringVal is not NULL. |
| } |
| |
| // String implements Stringer.String for NullString |
| func (n NullString) String() string { |
| if !n.Valid { |
| return fmt.Sprintf("%v", "<null>") |
| } |
| return fmt.Sprintf("%q", n.StringVal) |
| } |
| |
| // NullFloat64 represents a Cloud Spanner FLOAT64 that may be NULL. |
| type NullFloat64 struct { |
| Float64 float64 |
| Valid bool // Valid is true if Float64 is not NULL. |
| } |
| |
| // Cloud Spanner STRUCT (aka STRUCT) values (https://cloud.google.com/spanner/docs/data-types#struct-type) |
| // can be represented by a Go struct value. |
| // The spanner.StructType of such values is built from the field types and field tag information |
| // of the Go struct. If a field in the struct type definition has a "spanner:<field_name>" tag, |
| // then the value of the "spanner" key in the tag is used as the name for that field in the |
| // built spanner.StructType, otherwise the field name in the struct definition is used. To specify a |
| // field with an empty field name in a Cloud Spanner STRUCT type, use the `spanner:""` tag |
| // annotation against the corresponding field in the Go struct's type definition. |
| // |
| // A STRUCT value can contain STRUCT-typed and Array-of-STRUCT typed fields and these can be |
| // specified using named struct-typed and []struct-typed fields inside a Go struct. However, |
| // embedded struct fields are not allowed. Unexported struct fields are ignored. |
| // |
| // NULL STRUCT values in Cloud Spanner are typed. A nil pointer to a Go struct value can be used to |
| // specify a NULL STRUCT value of the corresponding spanner.StructType. Nil and empty slices of a |
| // Go STRUCT type can be used to specify NULL and empty array values respectively of the |
| // corresponding spanner.StructType. A slice of pointers to a Go struct type can be used to specify |
| // an array of NULL-able STRUCT values. |
| |
| // String implements Stringer.String for NullFloat64 |
| func (n NullFloat64) String() string { |
| if !n.Valid { |
| return fmt.Sprintf("%v", "<null>") |
| } |
| return fmt.Sprintf("%v", n.Float64) |
| } |
| |
| // NullBool represents a Cloud Spanner BOOL that may be NULL. |
| type NullBool struct { |
| Bool bool |
| Valid bool // Valid is true if Bool is not NULL. |
| } |
| |
| // String implements Stringer.String for NullBool |
| func (n NullBool) String() string { |
| if !n.Valid { |
| return fmt.Sprintf("%v", "<null>") |
| } |
| return fmt.Sprintf("%v", n.Bool) |
| } |
| |
| // NullTime represents a Cloud Spanner TIMESTAMP that may be null. |
| type NullTime struct { |
| Time time.Time |
| Valid bool // Valid is true if Time is not NULL. |
| } |
| |
| // String implements Stringer.String for NullTime |
| func (n NullTime) String() string { |
| if !n.Valid { |
| return fmt.Sprintf("%s", "<null>") |
| } |
| return fmt.Sprintf("%q", n.Time.Format(time.RFC3339Nano)) |
| } |
| |
| // NullDate represents a Cloud Spanner DATE that may be null. |
| type NullDate struct { |
| Date civil.Date |
| Valid bool // Valid is true if Date is not NULL. |
| } |
| |
| // String implements Stringer.String for NullDate |
| func (n NullDate) String() string { |
| if !n.Valid { |
| return fmt.Sprintf("%s", "<null>") |
| } |
| return fmt.Sprintf("%q", n.Date) |
| } |
| |
| // NullRow represents a Cloud Spanner STRUCT that may be NULL. |
| // See also the document for Row. |
| // Note that NullRow is not a valid Cloud Spanner column Type. |
| type NullRow struct { |
| Row Row |
| Valid bool // Valid is true if Row is not NULL. |
| } |
| |
| // GenericColumnValue represents the generic encoded value and type of the |
| // column. See google.spanner.v1.ResultSet proto for details. This can be |
| // useful for proxying query results when the result types are not known in |
| // advance. |
| // |
| // If you populate a GenericColumnValue from a row using Row.Column or related |
| // methods, do not modify the contents of Type and Value. |
| type GenericColumnValue struct { |
| Type *sppb.Type |
| Value *proto3.Value |
| } |
| |
| // Decode decodes a GenericColumnValue. The ptr argument should be a pointer |
| // to a Go value that can accept v. |
| func (v GenericColumnValue) Decode(ptr interface{}) error { |
| return decodeValue(v.Value, v.Type, ptr) |
| } |
| |
| // NewGenericColumnValue creates a GenericColumnValue from Go value that is |
| // valid for Cloud Spanner. |
| func newGenericColumnValue(v interface{}) (*GenericColumnValue, error) { |
| value, typ, err := encodeValue(v) |
| if err != nil { |
| return nil, err |
| } |
| return &GenericColumnValue{Value: value, Type: typ}, nil |
| } |
| |
| // errTypeMismatch returns error for destination not having a compatible type |
| // with source Cloud Spanner type. |
| func errTypeMismatch(srcCode, elCode sppb.TypeCode, dst interface{}) error { |
| s := srcCode.String() |
| if srcCode == sppb.TypeCode_ARRAY { |
| s = fmt.Sprintf("%v[%v]", srcCode, elCode) |
| } |
| return spannerErrorf(codes.InvalidArgument, "type %T cannot be used for decoding %s", dst, s) |
| } |
| |
| // errNilSpannerType returns error for nil Cloud Spanner type in decoding. |
| func errNilSpannerType() error { |
| return spannerErrorf(codes.FailedPrecondition, "unexpected nil Cloud Spanner data type in decoding") |
| } |
| |
| // errNilSrc returns error for decoding from nil proto value. |
| func errNilSrc() error { |
| return spannerErrorf(codes.FailedPrecondition, "unexpected nil Cloud Spanner value in decoding") |
| } |
| |
| // errNilDst returns error for decoding into nil interface{}. |
| func errNilDst(dst interface{}) error { |
| return spannerErrorf(codes.InvalidArgument, "cannot decode into nil type %T", dst) |
| } |
| |
| // errNilArrElemType returns error for input Cloud Spanner data type being a array but without a |
| // non-nil array element type. |
| func errNilArrElemType(t *sppb.Type) error { |
| return spannerErrorf(codes.FailedPrecondition, "array type %v is with nil array element type", t) |
| } |
| |
| func errUnsupportedEmbeddedStructFields(fname string) error { |
| return spannerErrorf(codes.InvalidArgument, "Embedded field: %s. Embedded and anonymous fields are not allowed "+ |
| "when converting Go structs to Cloud Spanner STRUCT values. To create a STRUCT value with an "+ |
| "unnamed field, use a `spanner:\"\"` field tag.", fname) |
| } |
| |
| // errDstNotForNull returns error for decoding a SQL NULL value into a destination which doesn't |
| // support NULL values. |
| func errDstNotForNull(dst interface{}) error { |
| return spannerErrorf(codes.InvalidArgument, "destination %T cannot support NULL SQL values", dst) |
| } |
| |
| // errBadEncoding returns error for decoding wrongly encoded types. |
| func errBadEncoding(v *proto3.Value, err error) error { |
| return spannerErrorf(codes.FailedPrecondition, "%v wasn't correctly encoded: <%v>", v, err) |
| } |
| |
| func parseNullTime(v *proto3.Value, p *NullTime, code sppb.TypeCode, isNull bool) error { |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_TIMESTAMP { |
| return errTypeMismatch(code, sppb.TypeCode_TYPE_CODE_UNSPECIFIED, p) |
| } |
| if isNull { |
| *p = NullTime{} |
| return nil |
| } |
| x, err := getStringValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := time.Parse(time.RFC3339Nano, x) |
| if err != nil { |
| return errBadEncoding(v, err) |
| } |
| p.Valid = true |
| p.Time = y |
| return nil |
| } |
| |
| // decodeValue decodes a protobuf Value into a pointer to a Go value, as |
| // specified by sppb.Type. |
| func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}) error { |
| if v == nil { |
| return errNilSrc() |
| } |
| if t == nil { |
| return errNilSpannerType() |
| } |
| code := t.Code |
| acode := sppb.TypeCode_TYPE_CODE_UNSPECIFIED |
| if code == sppb.TypeCode_ARRAY { |
| if t.ArrayElementType == nil { |
| return errNilArrElemType(t) |
| } |
| acode = t.ArrayElementType.Code |
| } |
| _, isNull := v.Kind.(*proto3.Value_NullValue) |
| |
| // Do the decoding based on the type of ptr. |
| switch p := ptr.(type) { |
| case nil: |
| return errNilDst(nil) |
| case *string: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_STRING { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| return errDstNotForNull(ptr) |
| } |
| x, err := getStringValue(v) |
| if err != nil { |
| return err |
| } |
| *p = x |
| case *NullString: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_STRING { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = NullString{} |
| break |
| } |
| x, err := getStringValue(v) |
| if err != nil { |
| return err |
| } |
| p.Valid = true |
| p.StringVal = x |
| case *[]NullString: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_STRING { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeNullStringArray(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *[]string: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_STRING { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeStringArray(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *[]byte: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_BYTES { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getStringValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := base64.StdEncoding.DecodeString(x) |
| if err != nil { |
| return errBadEncoding(v, err) |
| } |
| *p = y |
| case *[][]byte: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_BYTES { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeByteArray(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *int64: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_INT64 { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| return errDstNotForNull(ptr) |
| } |
| x, err := getStringValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := strconv.ParseInt(x, 10, 64) |
| if err != nil { |
| return errBadEncoding(v, err) |
| } |
| *p = y |
| case *NullInt64: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_INT64 { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = NullInt64{} |
| break |
| } |
| x, err := getStringValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := strconv.ParseInt(x, 10, 64) |
| if err != nil { |
| return errBadEncoding(v, err) |
| } |
| p.Valid = true |
| p.Int64 = y |
| case *[]NullInt64: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_INT64 { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeNullInt64Array(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *[]int64: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_INT64 { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeInt64Array(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *bool: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_BOOL { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| return errDstNotForNull(ptr) |
| } |
| x, err := getBoolValue(v) |
| if err != nil { |
| return err |
| } |
| *p = x |
| case *NullBool: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_BOOL { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = NullBool{} |
| break |
| } |
| x, err := getBoolValue(v) |
| if err != nil { |
| return err |
| } |
| p.Valid = true |
| p.Bool = x |
| case *[]NullBool: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_BOOL { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeNullBoolArray(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *[]bool: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_BOOL { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeBoolArray(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *float64: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_FLOAT64 { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| return errDstNotForNull(ptr) |
| } |
| x, err := getFloat64Value(v) |
| if err != nil { |
| return err |
| } |
| *p = x |
| case *NullFloat64: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_FLOAT64 { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = NullFloat64{} |
| break |
| } |
| x, err := getFloat64Value(v) |
| if err != nil { |
| return err |
| } |
| p.Valid = true |
| p.Float64 = x |
| case *[]NullFloat64: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_FLOAT64 { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeNullFloat64Array(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *[]float64: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_FLOAT64 { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeFloat64Array(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *time.Time: |
| var nt NullTime |
| if isNull { |
| return errDstNotForNull(ptr) |
| } |
| err := parseNullTime(v, &nt, code, isNull) |
| if err != nil { |
| return nil |
| } |
| *p = nt.Time |
| case *NullTime: |
| err := parseNullTime(v, p, code, isNull) |
| if err != nil { |
| return err |
| } |
| case *[]NullTime: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_TIMESTAMP { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeNullTimeArray(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *[]time.Time: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_TIMESTAMP { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeTimeArray(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *civil.Date: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_DATE { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| return errDstNotForNull(ptr) |
| } |
| x, err := getStringValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := civil.ParseDate(x) |
| if err != nil { |
| return errBadEncoding(v, err) |
| } |
| *p = y |
| case *NullDate: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if code != sppb.TypeCode_DATE { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = NullDate{} |
| break |
| } |
| x, err := getStringValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := civil.ParseDate(x) |
| if err != nil { |
| return errBadEncoding(v, err) |
| } |
| p.Valid = true |
| p.Date = y |
| case *[]NullDate: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_DATE { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeNullDateArray(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *[]civil.Date: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_DATE { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeDateArray(x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *[]NullRow: |
| if p == nil { |
| return errNilDst(p) |
| } |
| if acode != sppb.TypeCode_STRUCT { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| if isNull { |
| *p = nil |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| y, err := decodeRowArray(t.ArrayElementType.StructType, x) |
| if err != nil { |
| return err |
| } |
| *p = y |
| case *GenericColumnValue: |
| *p = GenericColumnValue{Type: t, Value: v} |
| default: |
| // Check if the proto encoding is for an array of structs. |
| if !(code == sppb.TypeCode_ARRAY && acode == sppb.TypeCode_STRUCT) { |
| return errTypeMismatch(code, acode, ptr) |
| } |
| vp := reflect.ValueOf(p) |
| if !vp.IsValid() { |
| return errNilDst(p) |
| } |
| if !isPtrStructPtrSlice(vp.Type()) { |
| // The container is not a pointer to a struct pointer slice. |
| return errTypeMismatch(code, acode, ptr) |
| } |
| // Only use reflection for nil detection on slow path. |
| // Also, IsNil panics on many types, so check it after the type check. |
| if vp.IsNil() { |
| return errNilDst(p) |
| } |
| if isNull { |
| // The proto Value is encoding NULL, set the pointer to struct |
| // slice to nil as well. |
| vp.Elem().Set(reflect.Zero(vp.Elem().Type())) |
| break |
| } |
| x, err := getListValue(v) |
| if err != nil { |
| return err |
| } |
| if err = decodeStructArray(t.ArrayElementType.StructType, x, p); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // errSrvVal returns an error for getting a wrong source protobuf value in decoding. |
| func errSrcVal(v *proto3.Value, want string) error { |
| return spannerErrorf(codes.FailedPrecondition, "cannot use %v(Kind: %T) as %s Value", |
| v, v.GetKind(), want) |
| } |
| |
| // getStringValue returns the string value encoded in proto3.Value v whose |
| // kind is proto3.Value_StringValue. |
| func getStringValue(v *proto3.Value) (string, error) { |
| if x, ok := v.GetKind().(*proto3.Value_StringValue); ok && x != nil { |
| return x.StringValue, nil |
| } |
| return "", errSrcVal(v, "String") |
| } |
| |
| // getBoolValue returns the bool value encoded in proto3.Value v whose |
| // kind is proto3.Value_BoolValue. |
| func getBoolValue(v *proto3.Value) (bool, error) { |
| if x, ok := v.GetKind().(*proto3.Value_BoolValue); ok && x != nil { |
| return x.BoolValue, nil |
| } |
| return false, errSrcVal(v, "Bool") |
| } |
| |
| // getListValue returns the proto3.ListValue contained in proto3.Value v whose |
| // kind is proto3.Value_ListValue. |
| func getListValue(v *proto3.Value) (*proto3.ListValue, error) { |
| if x, ok := v.GetKind().(*proto3.Value_ListValue); ok && x != nil { |
| return x.ListValue, nil |
| } |
| return nil, errSrcVal(v, "List") |
| } |
| |
| // errUnexpectedNumStr returns error for decoder getting a unexpected string for |
| // representing special float values. |
| func errUnexpectedNumStr(s string) error { |
| return spannerErrorf(codes.FailedPrecondition, "unexpected string value %q for number", s) |
| } |
| |
| // getFloat64Value returns the float64 value encoded in proto3.Value v whose |
| // kind is proto3.Value_NumberValue / proto3.Value_StringValue. |
| // Cloud Spanner uses string to encode NaN, Infinity and -Infinity. |
| func getFloat64Value(v *proto3.Value) (float64, error) { |
| switch x := v.GetKind().(type) { |
| case *proto3.Value_NumberValue: |
| if x == nil { |
| break |
| } |
| return x.NumberValue, nil |
| case *proto3.Value_StringValue: |
| if x == nil { |
| break |
| } |
| switch x.StringValue { |
| case "NaN": |
| return math.NaN(), nil |
| case "Infinity": |
| return math.Inf(1), nil |
| case "-Infinity": |
| return math.Inf(-1), nil |
| default: |
| return 0, errUnexpectedNumStr(x.StringValue) |
| } |
| } |
| return 0, errSrcVal(v, "Number") |
| } |
| |
| // errNilListValue returns error for unexpected nil ListValue in decoding Cloud Spanner ARRAYs. |
| func errNilListValue(sqlType string) error { |
| return spannerErrorf(codes.FailedPrecondition, "unexpected nil ListValue in decoding %v array", sqlType) |
| } |
| |
| // errDecodeArrayElement returns error for failure in decoding single array element. |
| func errDecodeArrayElement(i int, v proto.Message, sqlType string, err error) error { |
| se, ok := toSpannerError(err).(*Error) |
| if !ok { |
| return spannerErrorf(codes.Unknown, |
| "cannot decode %v(array element %v) as %v, error = <%v>", v, i, sqlType, err) |
| } |
| se.decorate(fmt.Sprintf("cannot decode %v(array element %v) as %v", v, i, sqlType)) |
| return se |
| } |
| |
| // decodeNullStringArray decodes proto3.ListValue pb into a NullString slice. |
| func decodeNullStringArray(pb *proto3.ListValue) ([]NullString, error) { |
| if pb == nil { |
| return nil, errNilListValue("STRING") |
| } |
| a := make([]NullString, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, stringType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "STRING", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeStringArray decodes proto3.ListValue pb into a string slice. |
| func decodeStringArray(pb *proto3.ListValue) ([]string, error) { |
| if pb == nil { |
| return nil, errNilListValue("STRING") |
| } |
| a := make([]string, len(pb.Values)) |
| st := stringType() |
| for i, v := range pb.Values { |
| if err := decodeValue(v, st, &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "STRING", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeNullInt64Array decodes proto3.ListValue pb into a NullInt64 slice. |
| func decodeNullInt64Array(pb *proto3.ListValue) ([]NullInt64, error) { |
| if pb == nil { |
| return nil, errNilListValue("INT64") |
| } |
| a := make([]NullInt64, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, intType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "INT64", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeInt64Array decodes proto3.ListValue pb into a int64 slice. |
| func decodeInt64Array(pb *proto3.ListValue) ([]int64, error) { |
| if pb == nil { |
| return nil, errNilListValue("INT64") |
| } |
| a := make([]int64, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, intType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "INT64", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeNullBoolArray decodes proto3.ListValue pb into a NullBool slice. |
| func decodeNullBoolArray(pb *proto3.ListValue) ([]NullBool, error) { |
| if pb == nil { |
| return nil, errNilListValue("BOOL") |
| } |
| a := make([]NullBool, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, boolType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "BOOL", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeBoolArray decodes proto3.ListValue pb into a bool slice. |
| func decodeBoolArray(pb *proto3.ListValue) ([]bool, error) { |
| if pb == nil { |
| return nil, errNilListValue("BOOL") |
| } |
| a := make([]bool, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, boolType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "BOOL", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeNullFloat64Array decodes proto3.ListValue pb into a NullFloat64 slice. |
| func decodeNullFloat64Array(pb *proto3.ListValue) ([]NullFloat64, error) { |
| if pb == nil { |
| return nil, errNilListValue("FLOAT64") |
| } |
| a := make([]NullFloat64, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, floatType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "FLOAT64", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeFloat64Array decodes proto3.ListValue pb into a float64 slice. |
| func decodeFloat64Array(pb *proto3.ListValue) ([]float64, error) { |
| if pb == nil { |
| return nil, errNilListValue("FLOAT64") |
| } |
| a := make([]float64, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, floatType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "FLOAT64", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeByteArray decodes proto3.ListValue pb into a slice of byte slice. |
| func decodeByteArray(pb *proto3.ListValue) ([][]byte, error) { |
| if pb == nil { |
| return nil, errNilListValue("BYTES") |
| } |
| a := make([][]byte, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, bytesType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "BYTES", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeNullTimeArray decodes proto3.ListValue pb into a NullTime slice. |
| func decodeNullTimeArray(pb *proto3.ListValue) ([]NullTime, error) { |
| if pb == nil { |
| return nil, errNilListValue("TIMESTAMP") |
| } |
| a := make([]NullTime, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, timeType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "TIMESTAMP", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeTimeArray decodes proto3.ListValue pb into a time.Time slice. |
| func decodeTimeArray(pb *proto3.ListValue) ([]time.Time, error) { |
| if pb == nil { |
| return nil, errNilListValue("TIMESTAMP") |
| } |
| a := make([]time.Time, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, timeType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "TIMESTAMP", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeNullDateArray decodes proto3.ListValue pb into a NullDate slice. |
| func decodeNullDateArray(pb *proto3.ListValue) ([]NullDate, error) { |
| if pb == nil { |
| return nil, errNilListValue("DATE") |
| } |
| a := make([]NullDate, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, dateType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "DATE", err) |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeDateArray decodes proto3.ListValue pb into a civil.Date slice. |
| func decodeDateArray(pb *proto3.ListValue) ([]civil.Date, error) { |
| if pb == nil { |
| return nil, errNilListValue("DATE") |
| } |
| a := make([]civil.Date, len(pb.Values)) |
| for i, v := range pb.Values { |
| if err := decodeValue(v, dateType(), &a[i]); err != nil { |
| return nil, errDecodeArrayElement(i, v, "DATE", err) |
| } |
| } |
| return a, nil |
| } |
| |
| func errNotStructElement(i int, v *proto3.Value) error { |
| return errDecodeArrayElement(i, v, "STRUCT", |
| spannerErrorf(codes.FailedPrecondition, "%v(type: %T) doesn't encode Cloud Spanner STRUCT", v, v)) |
| } |
| |
| // decodeRowArray decodes proto3.ListValue pb into a NullRow slice according to |
| // the structural information given in sppb.StructType ty. |
| func decodeRowArray(ty *sppb.StructType, pb *proto3.ListValue) ([]NullRow, error) { |
| if pb == nil { |
| return nil, errNilListValue("STRUCT") |
| } |
| a := make([]NullRow, len(pb.Values)) |
| for i := range pb.Values { |
| switch v := pb.Values[i].GetKind().(type) { |
| case *proto3.Value_ListValue: |
| a[i] = NullRow{ |
| Row: Row{ |
| fields: ty.Fields, |
| vals: v.ListValue.Values, |
| }, |
| Valid: true, |
| } |
| // Null elements not currently supported by the server, see |
| // https://cloud.google.com/spanner/docs/query-syntax#using-structs-with-select |
| case *proto3.Value_NullValue: |
| // no-op, a[i] is NullRow{} already |
| default: |
| return nil, errNotStructElement(i, pb.Values[i]) |
| } |
| } |
| return a, nil |
| } |
| |
| // errNilSpannerStructType returns error for unexpected nil Cloud Spanner STRUCT schema type in decoding. |
| func errNilSpannerStructType() error { |
| return spannerErrorf(codes.FailedPrecondition, "unexpected nil StructType in decoding Cloud Spanner STRUCT") |
| } |
| |
| // errUnnamedField returns error for decoding a Cloud Spanner STRUCT with unnamed field into a Go struct. |
| func errUnnamedField(ty *sppb.StructType, i int) error { |
| return spannerErrorf(codes.InvalidArgument, "unnamed field %v in Cloud Spanner STRUCT %+v", i, ty) |
| } |
| |
| // errNoOrDupGoField returns error for decoding a Cloud Spanner |
| // STRUCT into a Go struct which is either missing a field, or has duplicate fields. |
| func errNoOrDupGoField(s interface{}, f string) error { |
| return spannerErrorf(codes.InvalidArgument, "Go struct %+v(type %T) has no or duplicate fields for Cloud Spanner STRUCT field %v", s, s, f) |
| } |
| |
| // errDupColNames returns error for duplicated Cloud Spanner STRUCT field names found in decoding a Cloud Spanner STRUCT into a Go struct. |
| func errDupSpannerField(f string, ty *sppb.StructType) error { |
| return spannerErrorf(codes.InvalidArgument, "duplicated field name %q in Cloud Spanner STRUCT %+v", f, ty) |
| } |
| |
| // errDecodeStructField returns error for failure in decoding a single field of a Cloud Spanner STRUCT. |
| func errDecodeStructField(ty *sppb.StructType, f string, err error) error { |
| se, ok := toSpannerError(err).(*Error) |
| if !ok { |
| return spannerErrorf(codes.Unknown, |
| "cannot decode field %v of Cloud Spanner STRUCT %+v, error = <%v>", f, ty, err) |
| } |
| se.decorate(fmt.Sprintf("cannot decode field %v of Cloud Spanner STRUCT %+v", f, ty)) |
| return se |
| } |
| |
| // decodeStruct decodes proto3.ListValue pb into struct referenced by pointer ptr, according to |
| // the structural information given in sppb.StructType ty. |
| func decodeStruct(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}) error { |
| if reflect.ValueOf(ptr).IsNil() { |
| return errNilDst(ptr) |
| } |
| if ty == nil { |
| return errNilSpannerStructType() |
| } |
| // t holds the structural information of ptr. |
| t := reflect.TypeOf(ptr).Elem() |
| // v is the actual value that ptr points to. |
| v := reflect.ValueOf(ptr).Elem() |
| |
| fields, err := fieldCache.Fields(t) |
| if err != nil { |
| return toSpannerError(err) |
| } |
| seen := map[string]bool{} |
| for i, f := range ty.Fields { |
| if f.Name == "" { |
| return errUnnamedField(ty, i) |
| } |
| sf := fields.Match(f.Name) |
| if sf == nil { |
| return errNoOrDupGoField(ptr, f.Name) |
| } |
| if seen[f.Name] { |
| // We don't allow duplicated field name. |
| return errDupSpannerField(f.Name, ty) |
| } |
| // Try to decode a single field. |
| if err := decodeValue(pb.Values[i], f.Type, v.FieldByIndex(sf.Index).Addr().Interface()); err != nil { |
| return errDecodeStructField(ty, f.Name, err) |
| } |
| // Mark field f.Name as processed. |
| seen[f.Name] = true |
| } |
| return nil |
| } |
| |
| // isPtrStructPtrSlice returns true if ptr is a pointer to a slice of struct pointers. |
| func isPtrStructPtrSlice(t reflect.Type) bool { |
| if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Slice { |
| // t is not a pointer to a slice. |
| return false |
| } |
| if t = t.Elem(); t.Elem().Kind() != reflect.Ptr || t.Elem().Elem().Kind() != reflect.Struct { |
| // the slice that t points to is not a slice of struct pointers. |
| return false |
| } |
| return true |
| } |
| |
| // decodeStructArray decodes proto3.ListValue pb into struct slice referenced by pointer ptr, according to the |
| // structural information given in a sppb.StructType. |
| func decodeStructArray(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}) error { |
| if pb == nil { |
| return errNilListValue("STRUCT") |
| } |
| // Type of the struct pointers stored in the slice that ptr points to. |
| ts := reflect.TypeOf(ptr).Elem().Elem() |
| // The slice that ptr points to, might be nil at this point. |
| v := reflect.ValueOf(ptr).Elem() |
| // Allocate empty slice. |
| v.Set(reflect.MakeSlice(v.Type(), 0, len(pb.Values))) |
| // Decode every struct in pb.Values. |
| for i, pv := range pb.Values { |
| // Check if pv is a NULL value. |
| if _, isNull := pv.Kind.(*proto3.Value_NullValue); isNull { |
| // Append a nil pointer to the slice. |
| v.Set(reflect.Append(v, reflect.New(ts).Elem())) |
| continue |
| } |
| // Allocate empty struct. |
| s := reflect.New(ts.Elem()) |
| // Get proto3.ListValue l from proto3.Value pv. |
| l, err := getListValue(pv) |
| if err != nil { |
| return errDecodeArrayElement(i, pv, "STRUCT", err) |
| } |
| // Decode proto3.ListValue l into struct referenced by s.Interface(). |
| if err = decodeStruct(ty, l, s.Interface()); err != nil { |
| return errDecodeArrayElement(i, pv, "STRUCT", err) |
| } |
| // Append the decoded struct back into the slice. |
| v.Set(reflect.Append(v, s)) |
| } |
| return nil |
| } |
| |
| // errEncoderUnsupportedType returns error for not being able to encode a value of |
| // certain type. |
| func errEncoderUnsupportedType(v interface{}) error { |
| return spannerErrorf(codes.InvalidArgument, "client doesn't support type %T", v) |
| } |
| |
| // encodeValue encodes a Go native type into a proto3.Value. |
| func encodeValue(v interface{}) (*proto3.Value, *sppb.Type, error) { |
| pb := &proto3.Value{ |
| Kind: &proto3.Value_NullValue{NullValue: proto3.NullValue_NULL_VALUE}, |
| } |
| var pt *sppb.Type |
| var err error |
| switch v := v.(type) { |
| case nil: |
| case string: |
| pb.Kind = stringKind(v) |
| pt = stringType() |
| case NullString: |
| if v.Valid { |
| return encodeValue(v.StringVal) |
| } |
| pt = stringType() |
| case []string: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(stringType()) |
| case []NullString: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(stringType()) |
| case []byte: |
| if v != nil { |
| pb.Kind = stringKind(base64.StdEncoding.EncodeToString(v)) |
| } |
| pt = bytesType() |
| case [][]byte: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(bytesType()) |
| case int: |
| pb.Kind = stringKind(strconv.FormatInt(int64(v), 10)) |
| pt = intType() |
| case []int: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(intType()) |
| case int64: |
| pb.Kind = stringKind(strconv.FormatInt(v, 10)) |
| pt = intType() |
| case []int64: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(intType()) |
| case NullInt64: |
| if v.Valid { |
| return encodeValue(v.Int64) |
| } |
| pt = intType() |
| case []NullInt64: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(intType()) |
| case bool: |
| pb.Kind = &proto3.Value_BoolValue{BoolValue: v} |
| pt = boolType() |
| case []bool: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(boolType()) |
| case NullBool: |
| if v.Valid { |
| return encodeValue(v.Bool) |
| } |
| pt = boolType() |
| case []NullBool: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(boolType()) |
| case float64: |
| pb.Kind = &proto3.Value_NumberValue{NumberValue: v} |
| pt = floatType() |
| case []float64: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(floatType()) |
| case NullFloat64: |
| if v.Valid { |
| return encodeValue(v.Float64) |
| } |
| pt = floatType() |
| case []NullFloat64: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(floatType()) |
| case time.Time: |
| if v == commitTimestamp { |
| pb.Kind = stringKind(commitTimestampPlaceholderString) |
| } else { |
| pb.Kind = stringKind(v.UTC().Format(time.RFC3339Nano)) |
| } |
| pt = timeType() |
| case []time.Time: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(timeType()) |
| case NullTime: |
| if v.Valid { |
| return encodeValue(v.Time) |
| } |
| pt = timeType() |
| case []NullTime: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(timeType()) |
| case civil.Date: |
| pb.Kind = stringKind(v.String()) |
| pt = dateType() |
| case []civil.Date: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(dateType()) |
| case NullDate: |
| if v.Valid { |
| return encodeValue(v.Date) |
| } |
| pt = dateType() |
| case []NullDate: |
| if v != nil { |
| pb, err = encodeArray(len(v), func(i int) interface{} { return v[i] }) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| pt = listType(dateType()) |
| case GenericColumnValue: |
| // Deep clone to ensure subsequent changes to v before |
| // transmission don't affect our encoded value. |
| pb = proto.Clone(v.Value).(*proto3.Value) |
| pt = proto.Clone(v.Type).(*sppb.Type) |
| case []GenericColumnValue: |
| return nil, nil, errEncoderUnsupportedType(v) |
| default: |
| if !isStructOrArrayOfStructValue(v) { |
| return nil, nil, errEncoderUnsupportedType(v) |
| } |
| typ := reflect.TypeOf(v) |
| |
| // Value is a Go struct value/ptr. |
| if (typ.Kind() == reflect.Struct) || |
| (typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct) { |
| return encodeStruct(v) |
| } |
| |
| // Value is a slice of Go struct values/ptrs. |
| if typ.Kind() == reflect.Slice { |
| return encodeStructArray(v) |
| } |
| } |
| return pb, pt, nil |
| } |
| |
| // Encodes a Go struct value/ptr in v to the spanner Value and Type protos. v itself must |
| // be non-nil. |
| func encodeStruct(v interface{}) (*proto3.Value, *sppb.Type, error) { |
| typ := reflect.TypeOf(v) |
| val := reflect.ValueOf(v) |
| |
| // Pointer to struct. |
| if typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct { |
| typ = typ.Elem() |
| if val.IsNil() { |
| // nil pointer to struct, representing a NULL STRUCT value. Use a dummy value to |
| // get the type. |
| _, st, err := encodeStruct(reflect.Zero(typ).Interface()) |
| if err != nil { |
| return nil, nil, err |
| } |
| return nullProto(), st, nil |
| } |
| val = val.Elem() |
| } |
| |
| if typ.Kind() != reflect.Struct { |
| return nil, nil, errEncoderUnsupportedType(v) |
| } |
| |
| stf := make([]*sppb.StructType_Field, 0, typ.NumField()) |
| stv := make([]*proto3.Value, 0, typ.NumField()) |
| |
| for i := 0; i < typ.NumField(); i++ { |
| // If the field has a 'spanner' tag, use the value of that tag as the field name. |
| // This is used to build STRUCT types with unnamed/duplicate fields. |
| sf := typ.Field(i) |
| fval := val.Field(i) |
| |
| // Embedded fields are not allowed. |
| if sf.Anonymous { |
| return nil, nil, errUnsupportedEmbeddedStructFields(sf.Name) |
| } |
| |
| // Unexported fields are ignored. |
| if !fval.CanInterface() { |
| continue |
| } |
| |
| fname, ok := sf.Tag.Lookup("spanner") |
| if !ok { |
| fname = sf.Name |
| } |
| |
| eval, etype, err := encodeValue(fval.Interface()) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| stf = append(stf, mkField(fname, etype)) |
| stv = append(stv, eval) |
| } |
| |
| return listProto(stv...), structType(stf...), nil |
| } |
| |
| // Encodes a slice of Go struct values/ptrs in v to the spanner Value and Type protos. v itself |
| // must be non-nil. |
| func encodeStructArray(v interface{}) (*proto3.Value, *sppb.Type, error) { |
| etyp := reflect.TypeOf(v).Elem() |
| sliceval := reflect.ValueOf(v) |
| |
| // Slice of pointers to structs. |
| if etyp.Kind() == reflect.Ptr { |
| etyp = etyp.Elem() |
| } |
| |
| // Use a dummy struct value to get the element type |
| _, elemTyp, err := encodeStruct(reflect.Zero(etyp).Interface()) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| // nil slice represents a NULL array-of-struct. |
| if sliceval.IsNil() { |
| return nullProto(), listType(elemTyp), nil |
| } |
| |
| values := make([]*proto3.Value, 0, sliceval.Len()) |
| |
| for i := 0; i < sliceval.Len(); i++ { |
| ev, _, err := encodeStruct(sliceval.Index(i).Interface()) |
| if err != nil { |
| return nil, nil, err |
| } |
| values = append(values, ev) |
| } |
| return listProto(values...), listType(elemTyp), nil |
| } |
| |
| func isStructOrArrayOfStructValue(v interface{}) bool { |
| typ := reflect.TypeOf(v) |
| if typ.Kind() == reflect.Slice { |
| typ = typ.Elem() |
| } |
| if typ.Kind() == reflect.Ptr { |
| typ = typ.Elem() |
| } |
| return typ.Kind() == reflect.Struct |
| } |
| |
| func isSupportedMutationType(v interface{}) bool { |
| switch v.(type) { |
| case nil, string, NullString, []string, []NullString, |
| []byte, [][]byte, |
| int, []int, int64, []int64, NullInt64, []NullInt64, |
| bool, []bool, NullBool, []NullBool, |
| float64, []float64, NullFloat64, []NullFloat64, |
| time.Time, []time.Time, NullTime, []NullTime, |
| civil.Date, []civil.Date, NullDate, []NullDate, |
| GenericColumnValue: |
| return true |
| default: |
| return false |
| } |
| } |
| |
| // encodeValueArray encodes a Value array into a proto3.ListValue. |
| func encodeValueArray(vs []interface{}) (*proto3.ListValue, error) { |
| lv := &proto3.ListValue{} |
| lv.Values = make([]*proto3.Value, 0, len(vs)) |
| for _, v := range vs { |
| if !isSupportedMutationType(v) { |
| return nil, errEncoderUnsupportedType(v) |
| } |
| pb, _, err := encodeValue(v) |
| if err != nil { |
| return nil, err |
| } |
| lv.Values = append(lv.Values, pb) |
| } |
| return lv, nil |
| } |
| |
| // encodeArray assumes that all values of the array element type encode without error. |
| func encodeArray(len int, at func(int) interface{}) (*proto3.Value, error) { |
| vs := make([]*proto3.Value, len) |
| var err error |
| for i := 0; i < len; i++ { |
| vs[i], _, err = encodeValue(at(i)) |
| if err != nil { |
| return nil, err |
| } |
| } |
| return listProto(vs...), nil |
| } |
| |
| func spannerTagParser(t reflect.StructTag) (name string, keep bool, other interface{}, err error) { |
| if s := t.Get("spanner"); s != "" { |
| if s == "-" { |
| return "", false, nil, nil |
| } |
| return s, true, nil, nil |
| } |
| return "", true, nil, nil |
| } |
| |
| var fieldCache = fields.NewCache(spannerTagParser, nil, nil) |