Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(spanner): add support of UUID type #11345

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
13 changes: 12 additions & 1 deletion spanner/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import (
"cloud.google.com/go/spanner/internal"
pb "cloud.google.com/go/spanner/testdata/protos"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
"google.golang.org/api/iterator"
Expand Down Expand Up @@ -2034,7 +2035,9 @@ func TestIntegration_BasicTypes(t *testing.T) {
Numeric NUMERIC,
NumericArray ARRAY<NUMERIC>,
JSON JSON,
JSONArray ARRAY<JSON>
JSONArray ARRAY<JSON>,
UUID UUID,
UUIDArray Array<UUID>,
) PRIMARY KEY (RowID)`,
}
client, _, cleanup := prepareIntegrationTest(ctx, t, DefaultSessionPoolConfig, stmts)
Expand Down Expand Up @@ -2191,6 +2194,14 @@ func TestIntegration_BasicTypes(t *testing.T) {
{col: "NumericArray", val: nil, wantWithDefaultConfig: []NullNumeric(nil), wantWithNumber: []NullNumeric(nil)},
{col: "JSON", val: nil, wantWithDefaultConfig: NullJSON{}, wantWithNumber: NullJSON{}},
{col: "JSONArray", val: nil, wantWithDefaultConfig: []NullJSON(nil), wantWithNumber: []NullJSON(nil)},
{col: "UUID", val: uuid1},
{col: "UUID", val: uuid1, wantWithDefaultConfig: SpannerNullUUID{uuid1, true}, wantWithNumber: SpannerNullUUID{uuid1, true}},
{col: "UUID", val: SpannerNullUUID{uuid1, true}},
{col: "UUID", val: SpannerNullUUID{uuid1, true}, wantWithDefaultConfig: uuid1, wantWithNumber: uuid1},
{col: "UUID", val: SpannerNullUUID{uuid.UUID{}, false}},
{col: "UUIDArray", val: []uuid.UUID(nil), wantWithDefaultConfig: []SpannerNullUUID(nil), wantWithNumber: []SpannerNullUUID(nil)},
{col: "UUIDArray", val: []uuid.UUID{}, wantWithDefaultConfig: []SpannerNullUUID{}, wantWithNumber: []SpannerNullUUID{}},
{col: "UUIDArray", val: []uuid.UUID{uuid1, uuid2}, wantWithDefaultConfig: []SpannerNullUUID{{uuid1, true}, {uuid2, true}}, wantWithNumber: []SpannerNullUUID{{uuid1, true}, {uuid2, true}}},
}

// See https://github.com/GoogleCloudPlatform/cloud-spanner-emulator/issues/31
Expand Down
11 changes: 10 additions & 1 deletion spanner/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"cloud.google.com/go/civil"
sppb "cloud.google.com/go/spanner/apiv1/spannerpb"
"github.com/google/uuid"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/reflect/protoreflect"
proto3 "google.golang.org/protobuf/types/known/structpb"
Expand Down Expand Up @@ -85,7 +86,7 @@ func keyPartValue(part interface{}) (pb *proto3.Value, err error) {
pb, _, err = encodeValue(int64(v))
case uint32:
pb, _, err = encodeValue(int64(v))
case int64, float64, float32, NullInt64, NullFloat64, NullFloat32, bool, NullBool, []byte, string, NullString, time.Time, civil.Date, NullTime, NullDate, big.Rat, NullNumeric, protoreflect.Enum, NullProtoEnum:
case int64, float64, float32, NullInt64, NullFloat64, NullFloat32, bool, NullBool, []byte, string, NullString, time.Time, civil.Date, NullTime, NullDate, big.Rat, NullNumeric, protoreflect.Enum, NullProtoEnum, uuid.UUID, uuid.NullUUID, SpannerNullUUID:
pb, _, err = encodeValue(v)
case Encoder:
part, err = v.EncodeSpanner()
Expand Down Expand Up @@ -167,6 +168,14 @@ func (key Key) elemString(b *bytes.Buffer, part interface{}) {
fmt.Fprintf(b, "%q", v.Format(time.RFC3339Nano))
case big.Rat:
fmt.Fprintf(b, "%v", NumericString(&v))
case uuid.UUID:
fmt.Fprintf(b, "%s", v.String())
case uuid.NullUUID:
if !v.Valid {
fmt.Fprintf(b, "%s", nullString)
} else {
fmt.Fprintf(b, "%s", v.UUID.String())
}
case Encoder:
var err error
part, err = v.EncodeSpanner()
Expand Down
32 changes: 32 additions & 0 deletions spanner/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ package spanner

import (
"errors"
"fmt"
"math/big"
"testing"
"time"

"cloud.google.com/go/civil"
sppb "cloud.google.com/go/spanner/apiv1/spannerpb"
pb "cloud.google.com/go/spanner/testdata/protos"
"github.com/google/uuid"
proto3 "google.golang.org/protobuf/types/known/structpb"
)

Expand Down Expand Up @@ -139,6 +141,36 @@ func TestKey(t *testing.T) {
wantProto: listValueProto(stringProto("1.000000000")),
wantStr: `(1.000000000)`,
},
{
k: Key{uuid1},
wantProto: listValueProto(uuidProto(uuid1)),
wantStr: fmt.Sprintf("(%s)", uuid1.String()),
},
{
k: Key{uuid.NullUUID{UUID: uuid1, Valid: true}},
wantProto: listValueProto(uuidProto(uuid1)),
wantStr: fmt.Sprintf("(%s)", uuid1.String()),
},
{
k: Key{uuid.NullUUID{UUID: uuid1, Valid: false}},
wantProto: listValueProto(nullProto()),
wantStr: `(<null>)`,
},
{
k: Key{SpannerNullUUID{uuid1, false}},
wantProto: listValueProto(nullProto()),
wantStr: `(<null>)`,
},
{
k: Key{SpannerNullUUID{uuid1, true}},
wantProto: listValueProto(uuidProto(uuid1)),
wantStr: fmt.Sprintf("(%s)", uuid1.String()),
},
{
k: Key{SpannerNullUUID{uuid1, false}},
wantProto: listValueProto(nullProto()),
wantStr: `(<null>)`,
},
{
k: Key{[]byte("value")},
wantProto: listValueProto(bytesProto([]byte("value"))),
Expand Down
9 changes: 9 additions & 0 deletions spanner/protoutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"cloud.google.com/go/civil"
sppb "cloud.google.com/go/spanner/apiv1/spannerpb"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
proto3 "google.golang.org/protobuf/types/known/structpb"
Expand Down Expand Up @@ -123,6 +124,14 @@ func dateType() *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_DATE}
}

func uuidProto(u uuid.UUID) *proto3.Value {
return stringProto(u.String())
}

func uuidType() *sppb.Type {
return &sppb.Type{Code: sppb.TypeCode_UUID}
}

func listProto(p ...*proto3.Value) *proto3.Value {
return &proto3.Value{Kind: &proto3.Value_ListValue{ListValue: &proto3.ListValue{Values: p}}}
}
Expand Down
46 changes: 24 additions & 22 deletions spanner/row.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,28 +50,30 @@ import (
//
// 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
// *float32(not NULL), *NullFloat32 - FLOAT32
// *[]float32, *[]NullFloat32 - FLOAT32 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
// *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
// *float32(not NULL), *NullFloat32 - FLOAT32
// *[]float32, *[]NullFloat32 - FLOAT32 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
// *uuid.UUID(not NULL), *SpannerNullUuid - UUID
// *[]uuid.UUID, *[]SpannerNullUuid - UUID 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.
//
Expand Down
Loading
Loading