spanner: Allow decoding Spanner values to custom types.

This change adds support for decoding result set values from Spanner
into custom types that point back to supported types. This allows
decoding a result set into for example a struct like this:

type blobField []byte
type dbProductInfo struct {
    ProductID  int64     `spanner:"ProductID"`
    UpdateDate time.Time `spanner:"UpdateDate"`
    Data       blobField `spanner:"Data"`
}

Decoding Spanner column values to one of the known types
(string, []byte, int64, float64, time.Timestamp, civil.Date) and
their known Null<type> versions is done using a type switch
statement. Decoding to a custom type is done with reflection. The
reason for not using reflection for all types, including the known
base types, is the performance difference. Benchmark results executed
locally showing the difference between the two are shown below based
on decoding a single string value to a standard string or a custom
string type.

goos: linux
goarch: amd64
pkg: cloud.google.com/go/spanner
BenchmarkDecodeString-8         200000000   8.21 ns/op
BenchmarkDecodeCustomString-8   10000000    135  ns/op

Updates #1610.
Fixes #853.

Change-Id: Iad689b3704acadb83809375c762a7e0b564a7b2f
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/48090
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Tyler Bui-Palsulich <tbp@google.com>
Reviewed-by: Hengfeng Li <hengfeng@google.com>
diff --git a/spanner/row_test.go b/spanner/row_test.go
index 2a3ab8f..9a6ed29 100644
--- a/spanner/row_test.go
+++ b/spanner/row_test.go
@@ -25,8 +25,10 @@
 	"time"
 
 	"cloud.google.com/go/civil"
+	"cloud.google.com/go/internal/testutil"
 	proto "github.com/golang/protobuf/proto"
 	proto3 "github.com/golang/protobuf/ptypes/struct"
+	"github.com/google/go-cmp/cmp"
 	sppb "google.golang.org/genproto/googleapis/spanner/v1"
 )
 
@@ -1514,6 +1516,134 @@
 	}
 }
 
+// Test Row.ToStruct() with custom types.
+func TestToStructWithCustomTypes(t *testing.T) {
+	type CustomString string
+	type CustomNullString NullString
+	type CustomBytes []byte
+	type CustomInt64 int64
+	type CustomNullInt64 NullInt64
+	type CustomBool bool
+	type CustomNullBool NullBool
+	type CustomFloat64 float64
+	type CustomNullFloat64 NullFloat64
+	type CustomTime time.Time
+	type CustomNullTime NullTime
+	type CustomDate civil.Date
+	type CustomNullDate NullDate
+
+	s := []struct {
+		// STRING / STRING ARRAY
+		PrimaryKey      CustomString       `spanner:"STRING"`
+		NullString      CustomNullString   `spanner:"NULL_STRING"`
+		StringArray     []CustomNullString `spanner:"STRING_ARRAY"`
+		NullStringArray []CustomNullString `spanner:"NULL_STRING_ARRAY"`
+		// BYTES / BYTES ARRAY
+		Bytes          CustomBytes   `spanner:"BYTES"`
+		NullBytes      CustomBytes   `spanner:"NULL_BYTES"`
+		BytesArray     []CustomBytes `spanner:"BYTES_ARRAY"`
+		NullBytesArray []CustomBytes `spanner:"NULL_BYTES_ARRAY"`
+		// INT64 / INT64 ARRAY
+		Int64          CustomInt64       `spanner:"INT64"`
+		NullInt64      CustomNullInt64   `spanner:"NULL_INT64"`
+		Int64Array     []CustomNullInt64 `spanner:"INT64_ARRAY"`
+		NullInt64Array []CustomNullInt64 `spanner:"NULL_INT64_ARRAY"`
+		// BOOL / BOOL ARRAY
+		Bool          CustomBool       `spanner:"BOOL"`
+		NullBool      CustomNullBool   `spanner:"NULL_BOOL"`
+		BoolArray     []CustomNullBool `spanner:"BOOL_ARRAY"`
+		NullBoolArray []CustomNullBool `spanner:"NULL_BOOL_ARRAY"`
+		// FLOAT64 / FLOAT64 ARRAY
+		Float64          CustomFloat64       `spanner:"FLOAT64"`
+		NullFloat64      CustomNullFloat64   `spanner:"NULL_FLOAT64"`
+		Float64Array     []CustomNullFloat64 `spanner:"FLOAT64_ARRAY"`
+		NullFloat64Array []CustomNullFloat64 `spanner:"NULL_FLOAT64_ARRAY"`
+		// TIMESTAMP / TIMESTAMP ARRAY
+		Timestamp          CustomTime       `spanner:"TIMESTAMP"`
+		NullTimestamp      CustomNullTime   `spanner:"NULL_TIMESTAMP"`
+		TimestampArray     []CustomNullTime `spanner:"TIMESTAMP_ARRAY"`
+		NullTimestampArray []CustomNullTime `spanner:"NULL_TIMESTAMP_ARRAY"`
+		// DATE / DATE ARRAY
+		Date          CustomDate       `spanner:"DATE"`
+		NullDate      CustomNullDate   `spanner:"NULL_DATE"`
+		DateArray     []CustomNullDate `spanner:"DATE_ARRAY"`
+		NullDateArray []CustomNullDate `spanner:"NULL_DATE_ARRAY"`
+
+		// STRUCT ARRAY
+		StructArray []*struct {
+			Col1 CustomInt64
+			Col2 CustomFloat64
+			Col3 CustomString
+		} `spanner:"STRUCT_ARRAY"`
+		NullStructArray []*struct {
+			Col1 CustomInt64
+			Col2 CustomFloat64
+			Col3 CustomString
+		} `spanner:"NULL_STRUCT_ARRAY"`
+	}{
+		{}, // got
+		{
+			// STRING / STRING ARRAY
+			"value",
+			CustomNullString{},
+			[]CustomNullString{{"value1", true}, {}, {"value3", true}},
+			[]CustomNullString(nil),
+			// BYTES / BYTES ARRAY
+			CustomBytes("value"),
+			CustomBytes(nil),
+			[]CustomBytes{[]byte("value1"), nil, []byte("value3")},
+			[]CustomBytes(nil),
+			// INT64 / INT64 ARRAY
+			CustomInt64(17),
+			CustomNullInt64{},
+			[]CustomNullInt64{{int64(1), true}, {int64(2), true}, {}},
+			[]CustomNullInt64(nil),
+			// BOOL / BOOL ARRAY
+			true,
+			CustomNullBool{},
+			[]CustomNullBool{{}, {true, true}, {false, true}},
+			[]CustomNullBool(nil),
+			// FLOAT64 / FLOAT64 ARRAY
+			1.7,
+			CustomNullFloat64{},
+			[]CustomNullFloat64{{}, {}, {1.7, true}},
+			[]CustomNullFloat64(nil),
+			// TIMESTAMP / TIMESTAMP ARRAY
+			CustomTime(tm),
+			CustomNullTime{},
+			[]CustomNullTime{{}, {tm, true}},
+			[]CustomNullTime(nil),
+			// DATE / DATE ARRAY
+			CustomDate(dt),
+			CustomNullDate{},
+			[]CustomNullDate{{}, {dt, true}},
+			[]CustomNullDate(nil),
+			// STRUCT ARRAY
+			[]*struct {
+				Col1 CustomInt64
+				Col2 CustomFloat64
+				Col3 CustomString
+			}{
+				nil,
+
+				{3, 33.3, "three"},
+				nil,
+			},
+			[]*struct {
+				Col1 CustomInt64
+				Col2 CustomFloat64
+				Col3 CustomString
+			}(nil),
+		}, // want
+	}
+	err := row.ToStruct(&s[0])
+	if err != nil {
+		t.Errorf("row.ToStruct() returns error: %v, want nil", err)
+	} else if !testutil.Equal(s[0], s[1], cmp.AllowUnexported(CustomTime{})) {
+		t.Errorf("row.ToStruct() fetches struct %v, want %v", s[0], s[1])
+	}
+}
+
 func TestToStructEmbedded(t *testing.T) {
 	type (
 		S1 struct{ F1 string }
diff --git a/spanner/value.go b/spanner/value.go
index c9c9f9f..249a56c 100644
--- a/spanner/value.go
+++ b/spanner/value.go
@@ -26,7 +26,7 @@
 
 	"cloud.google.com/go/civil"
 	"cloud.google.com/go/internal/fields"
-	proto "github.com/golang/protobuf/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"
@@ -792,6 +792,15 @@
 	case *GenericColumnValue:
 		*p = GenericColumnValue{Type: t, Value: v}
 	default:
+		// Check if the pointer is a variant of a base type.
+		decodableType := getDecodableSpannerType(ptr)
+		if decodableType != spannerTypeUnknown {
+			if isNull && !decodableType.supportsNull() {
+				return errDstNotForNull(ptr)
+			}
+			return decodableType.decodeValueToCustomType(v, t, acode, ptr)
+		}
+
 		// 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)
@@ -826,6 +835,431 @@
 	return nil
 }
 
+// decodableSpannerType represents the Go types that a value from a Spanner
+// database can be converted to.
+type decodableSpannerType uint
+
+const (
+	spannerTypeUnknown decodableSpannerType = iota
+	spannerTypeInvalid
+	spannerTypeNonNullString
+	spannerTypeByteArray
+	spannerTypeNonNullInt64
+	spannerTypeNonNullBool
+	spannerTypeNonNullFloat64
+	spannerTypeNonNullTime
+	spannerTypeNonNullDate
+	spannerTypeNullString
+	spannerTypeNullInt64
+	spannerTypeNullBool
+	spannerTypeNullFloat64
+	spannerTypeNullTime
+	spannerTypeNullDate
+	spannerTypeArrayOfNonNullString
+	spannerTypeArrayOfByteArray
+	spannerTypeArrayOfNonNullInt64
+	spannerTypeArrayOfNonNullBool
+	spannerTypeArrayOfNonNullFloat64
+	spannerTypeArrayOfNonNullTime
+	spannerTypeArrayOfNonNullDate
+	spannerTypeArrayOfNullString
+	spannerTypeArrayOfNullInt64
+	spannerTypeArrayOfNullBool
+	spannerTypeArrayOfNullFloat64
+	spannerTypeArrayOfNullTime
+	spannerTypeArrayOfNullDate
+)
+
+// supportsNull returns true for the Go types that can hold a null value from
+// Spanner.
+func (d decodableSpannerType) supportsNull() bool {
+	switch d {
+	case spannerTypeNonNullString, spannerTypeNonNullInt64, spannerTypeNonNullBool, spannerTypeNonNullFloat64, spannerTypeNonNullTime, spannerTypeNonNullDate:
+		return false
+	default:
+		return true
+	}
+}
+
+// The following list of types represent the struct types that represent a
+// specific Spanner data type in Go. If a pointer to one of these types is
+// passed to decodeValue, the client library will decode one column value into
+// the struct. For pointers to all other struct types, the client library will
+// treat it as a generic struct that should contain a field for each column in
+// the result set that is being decoded.
+
+var typeOfNonNullTime = reflect.TypeOf(time.Time{})
+var typeOfNonNullDate = reflect.TypeOf(civil.Date{})
+var typeOfNullString = reflect.TypeOf(NullString{})
+var typeOfNullInt64 = reflect.TypeOf(NullInt64{})
+var typeOfNullBool = reflect.TypeOf(NullBool{})
+var typeOfNullFloat64 = reflect.TypeOf(NullFloat64{})
+var typeOfNullTime = reflect.TypeOf(NullTime{})
+var typeOfNullDate = reflect.TypeOf(NullDate{})
+
+// getDecodableSpannerType returns the corresponding decodableSpannerType of
+// the given pointer.
+func getDecodableSpannerType(ptr interface{}) decodableSpannerType {
+	kind := reflect.Indirect(reflect.ValueOf(ptr)).Kind()
+	if kind == reflect.Invalid {
+		return spannerTypeInvalid
+	}
+	switch kind {
+	case reflect.Invalid:
+		return spannerTypeInvalid
+	case reflect.String:
+		return spannerTypeNonNullString
+	case reflect.Int64:
+		return spannerTypeNonNullInt64
+	case reflect.Bool:
+		return spannerTypeNonNullBool
+	case reflect.Float64:
+		return spannerTypeNonNullFloat64
+	case reflect.Struct:
+		t := reflect.Indirect(reflect.ValueOf(ptr)).Type()
+		if t.ConvertibleTo(typeOfNonNullTime) {
+			return spannerTypeNonNullTime
+		}
+		if t.ConvertibleTo(typeOfNonNullDate) {
+			return spannerTypeNonNullDate
+		}
+		if t.ConvertibleTo(typeOfNullString) {
+			return spannerTypeNullString
+		}
+		if t.ConvertibleTo(typeOfNullInt64) {
+			return spannerTypeNullInt64
+		}
+		if t.ConvertibleTo(typeOfNullBool) {
+			return spannerTypeNullBool
+		}
+		if t.ConvertibleTo(typeOfNullFloat64) {
+			return spannerTypeNullFloat64
+		}
+		if t.ConvertibleTo(typeOfNullTime) {
+			return spannerTypeNullTime
+		}
+		if t.ConvertibleTo(typeOfNullDate) {
+			return spannerTypeNullDate
+		}
+	case reflect.Slice:
+		kind := reflect.Indirect(reflect.ValueOf(ptr)).Type().Elem().Kind()
+		switch kind {
+		case reflect.Invalid:
+			return spannerTypeUnknown
+		case reflect.String:
+			return spannerTypeArrayOfNonNullString
+		case reflect.Uint8:
+			return spannerTypeByteArray
+		case reflect.Int64:
+			return spannerTypeArrayOfNonNullInt64
+		case reflect.Bool:
+			return spannerTypeArrayOfNonNullBool
+		case reflect.Float64:
+			return spannerTypeArrayOfNonNullFloat64
+		case reflect.Struct:
+			t := reflect.Indirect(reflect.ValueOf(ptr)).Type().Elem()
+			if t.ConvertibleTo(typeOfNonNullTime) {
+				return spannerTypeArrayOfNonNullTime
+			}
+			if t.ConvertibleTo(typeOfNonNullDate) {
+				return spannerTypeArrayOfNonNullDate
+			}
+			if t.ConvertibleTo(typeOfNullString) {
+				return spannerTypeArrayOfNullString
+			}
+			if t.ConvertibleTo(typeOfNullInt64) {
+				return spannerTypeArrayOfNullInt64
+			}
+			if t.ConvertibleTo(typeOfNullBool) {
+				return spannerTypeArrayOfNullBool
+			}
+			if t.ConvertibleTo(typeOfNullFloat64) {
+				return spannerTypeArrayOfNullFloat64
+			}
+			if t.ConvertibleTo(typeOfNullTime) {
+				return spannerTypeArrayOfNullTime
+			}
+			if t.ConvertibleTo(typeOfNullDate) {
+				return spannerTypeArrayOfNullDate
+			}
+		case reflect.Slice:
+			// The only array-of-array type that is supported is [][]byte.
+			kind := reflect.Indirect(reflect.ValueOf(ptr)).Type().Elem().Elem().Kind()
+			switch kind {
+			case reflect.Uint8:
+				return spannerTypeArrayOfByteArray
+			}
+		}
+	}
+	// Not convertible to a known base type.
+	return spannerTypeUnknown
+}
+
+// decodeValueToCustomType decodes a protobuf Value into a pointer to a Go
+// value. It must be possible to convert the value to the type pointed to by
+// the pointer.
+func (dsc decodableSpannerType) decodeValueToCustomType(v *proto3.Value, t *sppb.Type, acode sppb.TypeCode, ptr interface{}) error {
+	code := t.Code
+	_, isNull := v.Kind.(*proto3.Value_NullValue)
+	if dsc == spannerTypeInvalid {
+		return errNilDst(ptr)
+	}
+	if isNull && !dsc.supportsNull() {
+		return errDstNotForNull(ptr)
+	}
+
+	var result interface{}
+	switch dsc {
+	case spannerTypeNonNullString, spannerTypeNullString:
+		if code != sppb.TypeCode_STRING {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			result = &NullString{}
+			break
+		}
+		x, err := getStringValue(v)
+		if err != nil {
+			return err
+		}
+		if dsc == spannerTypeNonNullString {
+			result = &x
+		} else {
+			result = &NullString{x, !isNull}
+		}
+	case spannerTypeByteArray:
+		if code != sppb.TypeCode_BYTES {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			result = []byte(nil)
+			break
+		}
+		x, err := getStringValue(v)
+		if err != nil {
+			return err
+		}
+		y, err := base64.StdEncoding.DecodeString(x)
+		if err != nil {
+			return errBadEncoding(v, err)
+		}
+		result = y
+	case spannerTypeNonNullInt64, spannerTypeNullInt64:
+		if code != sppb.TypeCode_INT64 {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			result = &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)
+		}
+		if dsc == spannerTypeNonNullInt64 {
+			result = &y
+		} else {
+			result = &NullInt64{y, !isNull}
+		}
+	case spannerTypeNonNullBool, spannerTypeNullBool:
+		if code != sppb.TypeCode_BOOL {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			result = &NullBool{}
+			break
+		}
+		x, err := getBoolValue(v)
+		if err != nil {
+			return err
+		}
+		if dsc == spannerTypeNonNullBool {
+			result = &x
+		} else {
+			result = &NullBool{x, !isNull}
+		}
+	case spannerTypeNonNullFloat64, spannerTypeNullFloat64:
+		if code != sppb.TypeCode_FLOAT64 {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			result = &NullFloat64{}
+			break
+		}
+		x, err := getFloat64Value(v)
+		if err != nil {
+			return err
+		}
+		if dsc == spannerTypeNonNullFloat64 {
+			result = &x
+		} else {
+			result = &NullFloat64{x, !isNull}
+		}
+	case spannerTypeNonNullTime, spannerTypeNullTime:
+		var nt NullTime
+		err := parseNullTime(v, &nt, code, isNull)
+		if err != nil {
+			return err
+		}
+		if dsc == spannerTypeNonNullTime {
+			result = &nt.Time
+		} else {
+			result = &nt
+		}
+	case spannerTypeNonNullDate, spannerTypeNullDate:
+		if code != sppb.TypeCode_DATE {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			result = &NullDate{}
+			break
+		}
+		x, err := getStringValue(v)
+		if err != nil {
+			return err
+		}
+		y, err := civil.ParseDate(x)
+		if err != nil {
+			return errBadEncoding(v, err)
+		}
+		if dsc == spannerTypeNonNullDate {
+			result = &y
+		} else {
+			result = &NullDate{y, !isNull}
+		}
+	case spannerTypeArrayOfNonNullString, spannerTypeArrayOfNullString:
+		if acode != sppb.TypeCode_STRING {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			ptr = nil
+			return nil
+		}
+		x, err := getListValue(v)
+		if err != nil {
+			return err
+		}
+		y, err := decodeGenericArray(reflect.TypeOf(ptr).Elem(), x, stringType(), "STRING")
+		if err != nil {
+			return err
+		}
+		result = y
+	case spannerTypeArrayOfByteArray:
+		if acode != sppb.TypeCode_BYTES {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			ptr = nil
+			return nil
+		}
+		x, err := getListValue(v)
+		if err != nil {
+			return err
+		}
+		y, err := decodeGenericArray(reflect.TypeOf(ptr).Elem(), x, bytesType(), "BYTES")
+		if err != nil {
+			return err
+		}
+		result = y
+	case spannerTypeArrayOfNonNullInt64, spannerTypeArrayOfNullInt64:
+		if acode != sppb.TypeCode_INT64 {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			ptr = nil
+			return nil
+		}
+		x, err := getListValue(v)
+		if err != nil {
+			return err
+		}
+		y, err := decodeGenericArray(reflect.TypeOf(ptr).Elem(), x, intType(), "INT64")
+		if err != nil {
+			return err
+		}
+		result = y
+	case spannerTypeArrayOfNonNullBool, spannerTypeArrayOfNullBool:
+		if acode != sppb.TypeCode_BOOL {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			ptr = nil
+			return nil
+		}
+		x, err := getListValue(v)
+		if err != nil {
+			return err
+		}
+		y, err := decodeGenericArray(reflect.TypeOf(ptr).Elem(), x, boolType(), "BOOL")
+		if err != nil {
+			return err
+		}
+		result = y
+	case spannerTypeArrayOfNonNullFloat64, spannerTypeArrayOfNullFloat64:
+		if acode != sppb.TypeCode_FLOAT64 {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			ptr = nil
+			return nil
+		}
+		x, err := getListValue(v)
+		if err != nil {
+			return err
+		}
+		y, err := decodeGenericArray(reflect.TypeOf(ptr).Elem(), x, floatType(), "FLOAT64")
+		if err != nil {
+			return err
+		}
+		result = y
+	case spannerTypeArrayOfNonNullTime, spannerTypeArrayOfNullTime:
+		if acode != sppb.TypeCode_TIMESTAMP {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			ptr = nil
+			return nil
+		}
+		x, err := getListValue(v)
+		if err != nil {
+			return err
+		}
+		y, err := decodeGenericArray(reflect.TypeOf(ptr).Elem(), x, timeType(), "TIMESTAMP")
+		if err != nil {
+			return err
+		}
+		result = y
+	case spannerTypeArrayOfNonNullDate, spannerTypeArrayOfNullDate:
+		if acode != sppb.TypeCode_DATE {
+			return errTypeMismatch(code, acode, ptr)
+		}
+		if isNull {
+			ptr = nil
+			return nil
+		}
+		x, err := getListValue(v)
+		if err != nil {
+			return err
+		}
+		y, err := decodeGenericArray(reflect.TypeOf(ptr).Elem(), x, dateType(), "DATE")
+		if err != nil {
+			return err
+		}
+		result = y
+	default:
+		// This should not be possible.
+		return fmt.Errorf("unknown decodable type found: %v", dsc)
+	}
+	source := reflect.Indirect(reflect.ValueOf(result))
+	destination := reflect.Indirect(reflect.ValueOf(ptr))
+	destination.Set(source.Convert(destination.Type()))
+	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",
@@ -909,6 +1343,21 @@
 	return se
 }
 
+// decodeGenericArray decodes proto3.ListValue pb into a slice which type is
+// determined through reflection.
+func decodeGenericArray(tp reflect.Type, pb *proto3.ListValue, t *sppb.Type, sqlType string) (interface{}, error) {
+	if pb == nil {
+		return nil, errNilListValue(sqlType)
+	}
+	a := reflect.MakeSlice(tp, len(pb.Values), len(pb.Values))
+	for i, v := range pb.Values {
+		if err := decodeValue(v, t, a.Index(i).Addr().Interface()); err != nil {
+			return nil, errDecodeArrayElement(i, v, "STRING", err)
+		}
+	}
+	return a.Interface(), nil
+}
+
 // decodeNullStringArray decodes proto3.ListValue pb into a NullString slice.
 func decodeNullStringArray(pb *proto3.ListValue) ([]NullString, error) {
 	if pb == nil {
diff --git a/spanner/value_benchmarks_test.go b/spanner/value_benchmarks_test.go
index 32ea2d7..cc897ab 100644
--- a/spanner/value_benchmarks_test.go
+++ b/spanner/value_benchmarks_test.go
@@ -98,6 +98,27 @@
 	}
 }
 
+func BenchmarkDecodeString(b *testing.B) {
+	v := stringProto("test")
+	t := stringType()
+	var s string
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		decodeValue(v, t, &s)
+	}
+}
+
+func BenchmarkDecodeCustomString(b *testing.B) {
+	v := stringProto("test")
+	t := stringType()
+	type CustomString string
+	var s CustomString
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		decodeValue(v, t, &s)
+	}
+}
+
 func BenchmarkDecodeArray(b *testing.B) {
 	for _, size := range []int{1, 10, 100, 1000} {
 		vals := make([]*proto3.Value, size)
diff --git a/spanner/value_test.go b/spanner/value_test.go
index b40f683..24016bd 100644
--- a/spanner/value_test.go
+++ b/spanner/value_test.go
@@ -23,8 +23,10 @@
 	"time"
 
 	"cloud.google.com/go/civil"
+	"cloud.google.com/go/internal/testutil"
 	"github.com/golang/protobuf/proto"
 	proto3 "github.com/golang/protobuf/ptypes/struct"
+	"github.com/google/go-cmp/cmp"
 	sppb "google.golang.org/genproto/googleapis/spanner/v1"
 )
 
@@ -697,97 +699,93 @@
 
 // Test decoding Values.
 func TestDecodeValue(t *testing.T) {
-	for i, test := range []struct {
-		in   *proto3.Value
-		t    *sppb.Type
-		want interface{}
-		fail bool
+	type CustomString string
+	type CustomBytes []byte
+	type CustomInt64 int64
+	type CustomBool bool
+	type CustomFloat64 float64
+	type CustomTime time.Time
+	type CustomDate civil.Date
+
+	type CustomNullString NullString
+	type CustomNullInt64 NullInt64
+	type CustomNullBool NullBool
+	type CustomNullFloat64 NullFloat64
+	type CustomNullTime NullTime
+	type CustomNullDate NullDate
+
+	for _, test := range []struct {
+		desc      string
+		proto     *proto3.Value
+		protoType *sppb.Type
+		want      interface{}
+		wantErr   bool
 	}{
 		// STRING
-		{stringProto("abc"), stringType(), "abc", false},
-		{nullProto(), stringType(), "abc", true},
-		{stringProto("abc"), stringType(), NullString{"abc", true}, false},
-		{nullProto(), stringType(), NullString{}, false},
+		{desc: "decode STRING to string", proto: stringProto("abc"), protoType: stringType(), want: "abc"},
+		{desc: "decode NULL to string", proto: nullProto(), protoType: stringType(), want: "abc", wantErr: true},
+		{desc: "decode STRING to NullString", proto: stringProto("abc"), protoType: stringType(), want: NullString{"abc", true}},
+		{desc: "decode NULL to NullString", proto: nullProto(), protoType: stringType(), want: NullString{}},
 		// STRING ARRAY with []NullString
-		{
-			listProto(stringProto("abc"), nullProto(), stringProto("bcd")),
-			listType(stringType()),
-			[]NullString{{"abc", true}, {}, {"bcd", true}},
-			false,
-		},
-		{nullProto(), listType(stringType()), []NullString(nil), false},
+		{desc: "decode ARRAY<STRING> to []NullString", proto: listProto(stringProto("abc"), nullProto(), stringProto("bcd")), protoType: listType(stringType()), want: []NullString{{"abc", true}, {}, {"bcd", true}}},
+		{desc: "decode NULL to []NullString", proto: nullProto(), protoType: listType(stringType()), want: []NullString(nil)},
 		// STRING ARRAY with []string
-		{
-			listProto(stringProto("abc"), stringProto("bcd")),
-			listType(stringType()),
-			[]string{"abc", "bcd"},
-			false,
-		},
+		{desc: "decode ARRAY<STRING> to []string", proto: listProto(stringProto("abc"), stringProto("bcd")), protoType: listType(stringType()), want: []string{"abc", "bcd"}},
 		// BYTES
-		{bytesProto([]byte("ab")), bytesType(), []byte("ab"), false},
-		{nullProto(), bytesType(), []byte(nil), false},
+		{desc: "decode BYTES to []byte", proto: bytesProto([]byte("ab")), protoType: bytesType(), want: []byte("ab")},
+		{desc: "decode NULL to []byte", proto: nullProto(), protoType: bytesType(), want: []byte(nil)},
 		// BYTES ARRAY
-		{listProto(bytesProto([]byte("ab")), nullProto()), listType(bytesType()), [][]byte{[]byte("ab"), nil}, false},
-		{nullProto(), listType(bytesType()), [][]byte(nil), false},
+		{desc: "decode ARRAY<BYTES> to [][]byte", proto: listProto(bytesProto([]byte("ab")), nullProto()), protoType: listType(bytesType()), want: [][]byte{[]byte("ab"), nil}},
+		{desc: "decode NULL to [][]byte", proto: nullProto(), protoType: listType(bytesType()), want: [][]byte(nil)},
 		//INT64
-		{intProto(15), intType(), int64(15), false},
-		{nullProto(), intType(), int64(0), true},
-		{intProto(15), intType(), NullInt64{15, true}, false},
-		{nullProto(), intType(), NullInt64{}, false},
+		{desc: "decode INT64 to int64", proto: intProto(15), protoType: intType(), want: int64(15)},
+		{desc: "decode NULL to int64", proto: nullProto(), protoType: intType(), want: int64(0), wantErr: true},
+		{desc: "decode INT64 to NullInt64", proto: intProto(15), protoType: intType(), want: NullInt64{15, true}},
+		{desc: "decode NULL to NullInt64", proto: nullProto(), protoType: intType(), want: NullInt64{}},
 		// INT64 ARRAY with []NullInt64
-		{listProto(intProto(91), nullProto(), intProto(87)), listType(intType()), []NullInt64{{91, true}, {}, {87, true}}, false},
-		{nullProto(), listType(intType()), []NullInt64(nil), false},
+		{desc: "decode ARRAY<INT64> to []NullInt64", proto: listProto(intProto(91), nullProto(), intProto(87)), protoType: listType(intType()), want: []NullInt64{{91, true}, {}, {87, true}}},
+		{desc: "decode NULL to []NullInt64", proto: nullProto(), protoType: listType(intType()), want: []NullInt64(nil)},
 		// INT64 ARRAY with []int64
-		{listProto(intProto(91), intProto(87)), listType(intType()), []int64{91, 87}, false},
+		{desc: "decode ARRAY<INT64> to []int64", proto: listProto(intProto(91), intProto(87)), protoType: listType(intType()), want: []int64{91, 87}},
 		// BOOL
-		{boolProto(true), boolType(), true, false},
-		{nullProto(), boolType(), true, true},
-		{boolProto(true), boolType(), NullBool{true, true}, false},
-		{nullProto(), boolType(), NullBool{}, false},
+		{desc: "decode BOOL to bool", proto: boolProto(true), protoType: boolType(), want: true},
+		{desc: "decode NULL to bool", proto: nullProto(), protoType: boolType(), want: true, wantErr: true},
+		{desc: "decode BOOL to NullBool", proto: boolProto(true), protoType: boolType(), want: NullBool{true, true}},
+		{desc: "decode BOOL to NullBool", proto: nullProto(), protoType: boolType(), want: NullBool{}},
 		// BOOL ARRAY with []NullBool
-		{listProto(boolProto(true), boolProto(false), nullProto()), listType(boolType()), []NullBool{{true, true}, {false, true}, {}}, false},
-		{nullProto(), listType(boolType()), []NullBool(nil), false},
+		{desc: "decode ARRAY<BOOL> to []NullBool", proto: listProto(boolProto(true), boolProto(false), nullProto()), protoType: listType(boolType()), want: []NullBool{{true, true}, {false, true}, {}}},
+		{desc: "decode NULL to []NullBool", proto: nullProto(), protoType: listType(boolType()), want: []NullBool(nil)},
 		// BOOL ARRAY with []bool
-		{listProto(boolProto(true), boolProto(false)), listType(boolType()), []bool{true, false}, false},
+		{desc: "decode ARRAY<BOOL> to []bool", proto: listProto(boolProto(true), boolProto(false)), protoType: listType(boolType()), want: []bool{true, false}},
 		// FLOAT64
-		{floatProto(3.14), floatType(), 3.14, false},
-		{nullProto(), floatType(), 0.00, true},
-		{floatProto(3.14), floatType(), NullFloat64{3.14, true}, false},
-		{nullProto(), floatType(), NullFloat64{}, false},
+		{desc: "decode FLOAT64 to float64", proto: floatProto(3.14), protoType: floatType(), want: 3.14},
+		{desc: "decode NULL to float64", proto: nullProto(), protoType: floatType(), want: 0.00, wantErr: true},
+		{desc: "decode FLOAT64 to NullFloat64", proto: floatProto(3.14), protoType: floatType(), want: NullFloat64{3.14, true}},
+		{desc: "decode NULL to NullFloat64", proto: nullProto(), protoType: floatType(), want: NullFloat64{}},
 		// FLOAT64 ARRAY with []NullFloat64
-		{
-			listProto(floatProto(math.Inf(1)), floatProto(math.Inf(-1)), nullProto(), floatProto(3.1)),
-			listType(floatType()),
-			[]NullFloat64{{math.Inf(1), true}, {math.Inf(-1), true}, {}, {3.1, true}},
-			false,
-		},
-		{nullProto(), listType(floatType()), []NullFloat64(nil), false},
+		{desc: "decode ARRAY<FLOAT64> to []NullFloat64", proto: listProto(floatProto(math.Inf(1)), floatProto(math.Inf(-1)), nullProto(), floatProto(3.1)), protoType: listType(floatType()), want: []NullFloat64{{math.Inf(1), true}, {math.Inf(-1), true}, {}, {3.1, true}}},
+		{desc: "decode NULL to []NullFloat64", proto: nullProto(), protoType: listType(floatType()), want: []NullFloat64(nil)},
 		// FLOAT64 ARRAY with []float64
-		{
-			listProto(floatProto(math.Inf(1)), floatProto(math.Inf(-1)), floatProto(3.1)),
-			listType(floatType()),
-			[]float64{math.Inf(1), math.Inf(-1), 3.1},
-			false,
-		},
+		{desc: "decode ARRAY<FLOAT64> to []float64", proto: listProto(floatProto(math.Inf(1)), floatProto(math.Inf(-1)), floatProto(3.1)), protoType: listType(floatType()), want: []float64{math.Inf(1), math.Inf(-1), 3.1}},
 		// TIMESTAMP
-		{timeProto(t1), timeType(), t1, false},
-		{timeProto(t1), timeType(), NullTime{t1, true}, false},
-		{nullProto(), timeType(), NullTime{}, false},
-		{intProto(7), timeType(), time.Time{}, true},
+		{desc: "decode TIMESTAMP to time.Time", proto: timeProto(t1), protoType: timeType(), want: t1},
+		{desc: "decode TIMESTAMP to NullTime", proto: timeProto(t1), protoType: timeType(), want: NullTime{t1, true}},
+		{desc: "decode NULL to NullTime", proto: nullProto(), protoType: timeType(), want: NullTime{}},
+		{desc: "decode INT64 to time.Time", proto: intProto(7), protoType: timeType(), want: time.Time{}, wantErr: true},
 		// TIMESTAMP ARRAY with []NullTime
-		{listProto(timeProto(t1), timeProto(t2), timeProto(t3), nullProto()), listType(timeType()), []NullTime{{t1, true}, {t2, true}, {t3, true}, {}}, false},
-		{nullProto(), listType(timeType()), []NullTime(nil), false},
+		{desc: "decode ARRAY<TIMESTAMP> to []NullTime", proto: listProto(timeProto(t1), timeProto(t2), timeProto(t3), nullProto()), protoType: listType(timeType()), want: []NullTime{{t1, true}, {t2, true}, {t3, true}, {}}},
+		{desc: "decode NULL to []NullTime", proto: nullProto(), protoType: listType(timeType()), want: []NullTime(nil)},
 		// TIMESTAMP ARRAY with []time.Time
-		{listProto(timeProto(t1), timeProto(t2), timeProto(t3)), listType(timeType()), []time.Time{t1, t2, t3}, false},
+		{desc: "decode ARRAY<TIMESTAMP> to []time.Time", proto: listProto(timeProto(t1), timeProto(t2), timeProto(t3)), protoType: listType(timeType()), want: []time.Time{t1, t2, t3}},
 		// DATE
-		{dateProto(d1), dateType(), d1, false},
-		{dateProto(d1), dateType(), NullDate{d1, true}, false},
-		{nullProto(), dateType(), NullDate{}, false},
+		{desc: "decode DATE to civil.Date", proto: dateProto(d1), protoType: dateType(), want: d1},
+		{desc: "decode DATE to NullDate", proto: dateProto(d1), protoType: dateType(), want: NullDate{d1, true}},
+		{desc: "decode NULL to NullDate", proto: nullProto(), protoType: dateType(), want: NullDate{}},
 		// DATE ARRAY with []NullDate
-		{listProto(dateProto(d1), dateProto(d2), nullProto()), listType(dateType()), []NullDate{{d1, true}, {d2, true}, {}}, false},
-		{nullProto(), listType(dateType()), []NullDate(nil), false},
+		{desc: "decode ARRAY<DATE> to []NullDate", proto: listProto(dateProto(d1), dateProto(d2), nullProto()), protoType: listType(dateType()), want: []NullDate{{d1, true}, {d2, true}, {}}},
+		{desc: "decode NULL to []NullDate", proto: nullProto(), protoType: listType(dateType()), want: []NullDate(nil)},
 		// DATE ARRAY with []civil.Date
-		{listProto(dateProto(d1), dateProto(d2)), listType(dateType()), []civil.Date{d1, d2}, false},
+		{desc: "decode ARRAY<DATE> to []civil.Date", proto: listProto(dateProto(d1), dateProto(d2)), protoType: listType(dateType()), want: []civil.Date{d1, d2}},
 		// STRUCT ARRAY
 		// STRUCT schema is equal to the following Go struct:
 		// type s struct {
@@ -798,7 +796,8 @@
 		//     }
 		// }
 		{
-			in: listProto(
+			desc: "decode ARRAY<STRUCT> to []NullRow",
+			proto: listProto(
 				listProto(
 					intProto(3),
 					listProto(
@@ -812,7 +811,7 @@
 				),
 				nullProto(),
 			),
-			t: listType(
+			protoType: listType(
 				structType(
 					mkField("Col1", intType()),
 					mkField(
@@ -874,10 +873,10 @@
 				},
 				{},
 			},
-			fail: false,
 		},
 		{
-			in: listProto(
+			desc: "decode ARRAY<STRUCT> to []*struct",
+			proto: listProto(
 				listProto(
 					intProto(3),
 					listProto(
@@ -891,7 +890,7 @@
 				),
 				nullProto(),
 			),
-			t: listType(
+			protoType: listType(
 				structType(
 					mkField("Col1", intType()),
 					mkField(
@@ -937,37 +936,99 @@
 				},
 				nil,
 			},
-			fail: false,
 		},
 		// GenericColumnValue
-		{stringProto("abc"), stringType(), GenericColumnValue{stringType(), stringProto("abc")}, false},
-		{nullProto(), stringType(), GenericColumnValue{stringType(), nullProto()}, false},
+		{desc: "decode STRING to GenericColumnValue", proto: stringProto("abc"), protoType: stringType(), want: GenericColumnValue{stringType(), stringProto("abc")}},
+		{desc: "decode NULL to GenericColumnValue", proto: nullProto(), protoType: stringType(), want: GenericColumnValue{stringType(), nullProto()}},
 		// not actually valid (stringProto inside int list), but demonstrates pass-through.
-		{
-			in: listProto(intProto(5), nullProto(), stringProto("bcd")),
-			t:  listType(intType()),
-			want: GenericColumnValue{
-				Type:  listType(intType()),
-				Value: listProto(intProto(5), nullProto(), stringProto("bcd")),
-			},
-			fail: false,
-		},
+		{desc: "decode ARRAY<INT64> to GenericColumnValue", proto: listProto(intProto(5), nullProto(), stringProto("bcd")), protoType: listType(intType()), want: GenericColumnValue{Type: listType(intType()), Value: listProto(intProto(5), nullProto(), stringProto("bcd"))}},
+
+		// Custom base types.
+		{desc: "decode STRING to CustomString", proto: stringProto("bar"), protoType: stringType(), want: CustomString("bar")},
+		{desc: "decode BYTES to CustomBytes", proto: bytesProto([]byte("ab")), protoType: bytesType(), want: CustomBytes("ab")},
+		{desc: "decode INT64 to CustomInt64", proto: intProto(-100), protoType: intType(), want: CustomInt64(-100)},
+		{desc: "decode BOOL to CustomBool", proto: boolProto(true), protoType: boolType(), want: CustomBool(true)},
+		{desc: "decode FLOAT64 to CustomFloat64", proto: floatProto(6.626), protoType: floatType(), want: CustomFloat64(6.626)},
+		{desc: "decode TIMESTAMP to CustomTimestamp", proto: timeProto(t1), protoType: timeType(), want: CustomTime(t1)},
+		{desc: "decode DATE to CustomDate", proto: dateProto(d1), protoType: dateType(), want: CustomDate(d1)},
+
+		{desc: "decode NULL to CustomString", proto: nullProto(), protoType: stringType(), want: CustomString(""), wantErr: true},
+		{desc: "decode NULL to CustomBytes", proto: nullProto(), protoType: bytesType(), want: CustomBytes(nil)},
+		{desc: "decode NULL to CustomInt64", proto: nullProto(), protoType: intType(), want: CustomInt64(0), wantErr: true},
+		{desc: "decode NULL to CustomBool", proto: nullProto(), protoType: boolType(), want: CustomBool(false), wantErr: true},
+		{desc: "decode NULL to CustomFloat64", proto: nullProto(), protoType: floatType(), want: CustomFloat64(0), wantErr: true},
+		{desc: "decode NULL to CustomTime", proto: nullProto(), protoType: timeType(), want: CustomTime{}, wantErr: true},
+		{desc: "decode NULL to CustomDate", proto: nullProto(), protoType: dateType(), want: CustomDate{}, wantErr: true},
+
+		{desc: "decode STRING to CustomNullString", proto: stringProto("bar"), protoType: stringType(), want: CustomNullString{"bar", true}},
+		{desc: "decode INT64 to CustomNullInt64", proto: intProto(-100), protoType: intType(), want: CustomNullInt64{-100, true}},
+		{desc: "decode BOOL to CustomNullBool", proto: boolProto(true), protoType: boolType(), want: CustomNullBool{true, true}},
+		{desc: "decode FLOAT64 to CustomNullFloat64", proto: floatProto(6.626), protoType: floatType(), want: CustomNullFloat64{6.626, true}},
+		{desc: "decode TIMESTAMP to CustomNullTime", proto: timeProto(t1), protoType: timeType(), want: CustomNullTime{t1, true}},
+		{desc: "decode DATE to CustomNullDate", proto: dateProto(d1), protoType: dateType(), want: CustomNullDate{d1, true}},
+
+		{desc: "decode NULL to CustomNullString", proto: nullProto(), protoType: stringType(), want: CustomNullString{}},
+		{desc: "decode NULL to CustomNullInt64", proto: nullProto(), protoType: intType(), want: CustomNullInt64{}},
+		{desc: "decode NULL to CustomNullBool", proto: nullProto(), protoType: boolType(), want: CustomNullBool{}},
+		{desc: "decode NULL to CustomNullFloat64", proto: nullProto(), protoType: floatType(), want: CustomNullFloat64{}},
+		{desc: "decode NULL to CustomNullTime", proto: nullProto(), protoType: timeType(), want: CustomNullTime{}},
+		{desc: "decode NULL to CustomNullDate", proto: nullProto(), protoType: dateType(), want: CustomNullDate{}},
+
+		// STRING ARRAY
+		{desc: "decode NULL to []CustomString", proto: nullProto(), protoType: listType(stringType()), want: []CustomString(nil)},
+		{desc: "decode ARRAY<STRING> to []CustomString", proto: listProto(stringProto("abc"), stringProto("bcd")), protoType: listType(stringType()), want: []CustomString{"abc", "bcd"}},
+		{desc: "decode ARRAY<STRING> with NULL values to []CustomString", proto: listProto(stringProto("abc"), nullProto(), stringProto("bcd")), protoType: listType(stringType()), want: []CustomString{}, wantErr: true},
+		{desc: "decode NULL to []CustomNullString", proto: nullProto(), protoType: listType(stringType()), want: []CustomNullString(nil)},
+		{desc: "decode ARRAY<STRING> to []CustomNullString", proto: listProto(stringProto("abc"), nullProto(), stringProto("bcd")), protoType: listType(stringType()), want: []CustomNullString{{"abc", true}, {}, {"bcd", true}}},
+		// BYTES ARRAY
+		{desc: "decode NULL to []CustomBytes", proto: nullProto(), protoType: listType(bytesType()), want: []CustomBytes(nil)},
+		{desc: "decode ARRAY<BYTES> to []CustomBytes", proto: listProto(bytesProto([]byte("abc")), nullProto(), bytesProto([]byte("bcd"))), protoType: listType(bytesType()), want: []CustomBytes{CustomBytes("abc"), CustomBytes(nil), CustomBytes("bcd")}},
+		// INT64 ARRAY
+		{desc: "decode NULL to []CustomInt64", proto: nullProto(), protoType: listType(intType()), want: []CustomInt64(nil)},
+		{desc: "decode ARRAY<INT64> with NULL values to []CustomInt64", proto: listProto(intProto(-100), nullProto(), intProto(100)), protoType: listType(intType()), want: []CustomInt64{}, wantErr: true},
+		{desc: "decode ARRAY<INT64> to []CustomInt64", proto: listProto(intProto(-100), intProto(100)), protoType: listType(intType()), want: []CustomInt64{-100, 100}},
+		{desc: "decode NULL to []CustomNullInt64", proto: nullProto(), protoType: listType(intType()), want: []CustomNullInt64(nil)},
+		{desc: "decode ARRAY<INT64> to []CustomNullInt64", proto: listProto(intProto(-100), nullProto(), intProto(100)), protoType: listType(intType()), want: []CustomNullInt64{{-100, true}, {}, {100, true}}},
+		// BOOL ARRAY
+		{desc: "decode NULL to []CustomBool", proto: nullProto(), protoType: listType(boolType()), want: []CustomBool(nil)},
+		{desc: "decode ARRAY<BOOL> with NULL values to []CustomBool", proto: listProto(boolProto(false), nullProto(), boolProto(true)), protoType: listType(boolType()), want: []CustomBool{}, wantErr: true},
+		{desc: "decode ARRAY<BOOL> to []CustomBool", proto: listProto(boolProto(false), boolProto(true)), protoType: listType(boolType()), want: []CustomBool{false, true}},
+		{desc: "decode NULL to []CustomNullBool", proto: nullProto(), protoType: listType(boolType()), want: []CustomNullBool(nil)},
+		{desc: "decode ARRAY<BOOL> to []CustomNullBool", proto: listProto(boolProto(false), nullProto(), boolProto(true)), protoType: listType(boolType()), want: []CustomNullBool{{false, true}, {}, {true, true}}},
+		// FLOAT64 ARRAY
+		{desc: "decode NULL to []CustomFloat64", proto: nullProto(), protoType: listType(floatType()), want: []CustomFloat64(nil)},
+		{desc: "decode ARRAY<FLOAT64> with NULL values to []CustomFloat64", proto: listProto(floatProto(3.14), nullProto(), floatProto(6.626)), protoType: listType(floatType()), want: []CustomFloat64{}, wantErr: true},
+		{desc: "decode ARRAY<FLOAT64> to []CustomFloat64", proto: listProto(floatProto(3.14), floatProto(6.626)), protoType: listType(floatType()), want: []CustomFloat64{3.14, 6.626}},
+		{desc: "decode NULL to []CustomNullFloat64", proto: nullProto(), protoType: listType(floatType()), want: []CustomNullFloat64(nil)},
+		{desc: "decode ARRAY<FLOAT64> to []CustomNullFloat64", proto: listProto(floatProto(3.14), nullProto(), floatProto(6.626)), protoType: listType(floatType()), want: []CustomNullFloat64{{3.14, true}, {}, {6.626, true}}},
+		// TIME ARRAY
+		{desc: "decode NULL to []CustomTime", proto: nullProto(), protoType: listType(timeType()), want: []CustomTime(nil)},
+		{desc: "decode ARRAY<TIMESTAMP> with NULL values to []CustomTime", proto: listProto(timeProto(t1), nullProto(), timeProto(t2)), protoType: listType(timeType()), want: []CustomTime{}, wantErr: true},
+		{desc: "decode ARRAY<TIMESTAMP> to []CustomTime", proto: listProto(timeProto(t1), timeProto(t2)), protoType: listType(timeType()), want: []CustomTime{CustomTime(t1), CustomTime(t2)}},
+		{desc: "decode NULL to []CustomNullTime", proto: nullProto(), protoType: listType(timeType()), want: []CustomNullTime(nil)},
+		{desc: "decode ARRAY<TIMESTAMP> to []CustomNullTime", proto: listProto(timeProto(t1), nullProto(), timeProto(t2)), protoType: listType(timeType()), want: []CustomNullTime{{t1, true}, {}, {t2, true}}},
+		// DATE ARRAY
+		{desc: "decode NULL to []CustomDate", proto: nullProto(), protoType: listType(dateType()), want: []CustomDate(nil)},
+		{desc: "decode ARRAY<DATE> with NULL values to []CustomDate", proto: listProto(dateProto(d1), nullProto(), dateProto(d2)), protoType: listType(dateType()), want: []CustomDate{}, wantErr: true},
+		{desc: "decode ARRAY<DATE> to []CustomDate", proto: listProto(dateProto(d1), dateProto(d2)), protoType: listType(dateType()), want: []CustomDate{CustomDate(d1), CustomDate(d2)}},
+		{desc: "decode NULL to []CustomNullDate", proto: nullProto(), protoType: listType(dateType()), want: []CustomNullDate(nil)},
+		{desc: "decode ARRAY<DATE> to []CustomNullDate", proto: listProto(dateProto(d1), nullProto(), dateProto(d2)), protoType: listType(dateType()), want: []CustomNullDate{{d1, true}, {}, {d2, true}}},
 	} {
 		gotp := reflect.New(reflect.TypeOf(test.want))
-		if err := decodeValue(test.in, test.t, gotp.Interface()); err != nil {
-			if !test.fail {
-				t.Errorf("%d: cannot decode %v(%v): %v", i, test.in, test.t, err)
+		err := decodeValue(test.proto, test.protoType, gotp.Interface())
+		if test.wantErr {
+			if err == nil {
+				t.Errorf("%s: missing expected decode failure for %v(%v)", test.desc, test.proto, test.protoType)
 			}
 			continue
 		}
-		if test.fail {
-			t.Errorf("%d: decoding %v(%v) succeeds unexpectedly, want error", i, test.in, test.t)
+		if err != nil {
+			t.Errorf("%s: cannot decode %v(%v): %v", test.desc, test.proto, test.protoType, err)
 			continue
 		}
 		got := reflect.Indirect(gotp).Interface()
-		if !testEqual(got, test.want) {
-			t.Errorf("%d: unexpected decoding result - got %v, want %v", i, got, test.want)
-			continue
+		if !testutil.Equal(got, test.want, cmp.AllowUnexported(CustomTime{}, CustomDate{}, Row{})) {
+			t.Errorf("%s: unexpected decoding result - got %v (%T), want %v (%T)", test.desc, got, got, test.want, test.want)
 		}
 	}
 }
@@ -991,6 +1052,101 @@
 	}
 }
 
+func TestGetDecodableSpannerType(t *testing.T) {
+	type CustomString string
+	type CustomInt64 int64
+	type CustomBool bool
+	type CustomFloat64 float64
+	type CustomTime time.Time
+	type CustomDate civil.Date
+
+	type CustomNullString NullString
+	type CustomNullInt64 NullInt64
+	type CustomNullBool NullBool
+	type CustomNullFloat64 NullFloat64
+	type CustomNullTime NullTime
+	type CustomNullDate NullDate
+
+	type StringEmbedded struct {
+		string
+	}
+	type NullStringEmbedded struct {
+		NullString
+	}
+
+	for i, test := range []struct {
+		in   interface{}
+		want decodableSpannerType
+	}{
+		{"foo", spannerTypeNonNullString},
+		{[]byte("ab"), spannerTypeByteArray},
+		{[]byte(nil), spannerTypeByteArray},
+		{int64(123), spannerTypeNonNullInt64},
+		{true, spannerTypeNonNullBool},
+		{3.14, spannerTypeNonNullFloat64},
+		{time.Now(), spannerTypeNonNullTime},
+		{civil.DateOf(time.Now()), spannerTypeNonNullDate},
+		{NullString{}, spannerTypeNullString},
+		{NullInt64{}, spannerTypeNullInt64},
+		{NullBool{}, spannerTypeNullBool},
+		{NullFloat64{}, spannerTypeNullFloat64},
+		{NullTime{}, spannerTypeNullTime},
+		{NullDate{}, spannerTypeNullDate},
+
+		{[]string{"foo", "bar"}, spannerTypeArrayOfNonNullString},
+		{[][]byte{{1, 2, 3}, {3, 2, 1}}, spannerTypeArrayOfByteArray},
+		{[][]byte{}, spannerTypeArrayOfByteArray},
+		{[]int64{int64(123)}, spannerTypeArrayOfNonNullInt64},
+		{[]bool{true}, spannerTypeArrayOfNonNullBool},
+		{[]float64{3.14}, spannerTypeArrayOfNonNullFloat64},
+		{[]time.Time{time.Now()}, spannerTypeArrayOfNonNullTime},
+		{[]civil.Date{civil.DateOf(time.Now())}, spannerTypeArrayOfNonNullDate},
+		{[]NullString{}, spannerTypeArrayOfNullString},
+		{[]NullInt64{}, spannerTypeArrayOfNullInt64},
+		{[]NullBool{}, spannerTypeArrayOfNullBool},
+		{[]NullFloat64{}, spannerTypeArrayOfNullFloat64},
+		{[]NullTime{}, spannerTypeArrayOfNullTime},
+		{[]NullDate{}, spannerTypeArrayOfNullDate},
+
+		{CustomString("foo"), spannerTypeNonNullString},
+		{CustomInt64(-100), spannerTypeNonNullInt64},
+		{CustomBool(true), spannerTypeNonNullBool},
+		{CustomFloat64(3.141592), spannerTypeNonNullFloat64},
+		{CustomTime(time.Now()), spannerTypeNonNullTime},
+		{CustomDate(civil.DateOf(time.Now())), spannerTypeNonNullDate},
+
+		{[]CustomString{}, spannerTypeArrayOfNonNullString},
+		{[]CustomInt64{}, spannerTypeArrayOfNonNullInt64},
+		{[]CustomBool{}, spannerTypeArrayOfNonNullBool},
+		{[]CustomFloat64{}, spannerTypeArrayOfNonNullFloat64},
+		{[]CustomTime{}, spannerTypeArrayOfNonNullTime},
+		{[]CustomDate{}, spannerTypeArrayOfNonNullDate},
+
+		{CustomNullString{}, spannerTypeNullString},
+		{CustomNullInt64{}, spannerTypeNullInt64},
+		{CustomNullBool{}, spannerTypeNullBool},
+		{CustomNullFloat64{}, spannerTypeNullFloat64},
+		{CustomNullTime{}, spannerTypeNullTime},
+		{CustomNullDate{}, spannerTypeNullDate},
+
+		{[]CustomNullString{}, spannerTypeArrayOfNullString},
+		{[]CustomNullInt64{}, spannerTypeArrayOfNullInt64},
+		{[]CustomNullBool{}, spannerTypeArrayOfNullBool},
+		{[]CustomNullFloat64{}, spannerTypeArrayOfNullFloat64},
+		{[]CustomNullTime{}, spannerTypeArrayOfNullTime},
+		{[]CustomNullDate{}, spannerTypeArrayOfNullDate},
+
+		{StringEmbedded{}, spannerTypeUnknown},
+		{NullStringEmbedded{}, spannerTypeUnknown},
+	} {
+		gotp := reflect.New(reflect.TypeOf(test.in))
+		got := getDecodableSpannerType(gotp.Interface())
+		if got != test.want {
+			t.Errorf("%d: unexpected decodable type - got %v, want %v", i, got, test.want)
+		}
+	}
+}
+
 // Test NaN encoding/decoding.
 func TestNaN(t *testing.T) {
 	// Decode NaN value.
@@ -1072,6 +1228,8 @@
 }
 
 func TestDecodeStruct(t *testing.T) {
+	type CustomString string
+	type CustomTime time.Time
 	stype := &sppb.StructType{Fields: []*sppb.StructType_Field{
 		{Name: "Id", Type: stringType()},
 		{Name: "Time", Type: timeType()},
@@ -1087,32 +1245,67 @@
 			ID   string
 			Time string
 		}
+		S3 struct {
+			ID   CustomString
+			Time CustomTime
+		}
+		S4 struct {
+			ID   CustomString
+			Time CustomString
+		}
+		S5 struct {
+			NullString
+			Time CustomTime
+		}
 	)
 	var (
 		s1 S1
 		s2 S2
+		s3 S3
+		s4 S4
+		s5 S5
 	)
 
-	for i, test := range []struct {
+	for _, test := range []struct {
+		desc string
 		ptr  interface{}
 		want interface{}
 		fail bool
 	}{
 		{
+			desc: "decode to S1",
 			ptr:  &s1,
 			want: &S1{ID: "id", Time: t1},
 		},
 		{
+			desc: "decode to S2",
 			ptr:  &s2,
 			fail: true,
 		},
+		{
+			desc: "decode to S3",
+			ptr:  &s3,
+			want: &S3{ID: CustomString("id"), Time: CustomTime(t1)},
+		},
+		{
+			desc: "decode to S4",
+			ptr:  &s4,
+			fail: true,
+		},
+		{
+			desc: "decode to S5",
+			ptr:  &s5,
+			fail: true,
+		},
 	} {
 		err := decodeStruct(stype, lv, test.ptr)
 		if (err != nil) != test.fail {
-			t.Errorf("#%d: got error %v, wanted fail: %v", i, err, test.fail)
+			t.Errorf("%s: got error %v, wanted fail: %v", test.desc, err, test.fail)
 		}
-		if err == nil && !testEqual(test.ptr, test.want) {
-			t.Errorf("#%d: got %+v, want %+v", i, test.ptr, test.want)
+		if err == nil {
+			if !testutil.Equal(test.ptr, test.want, cmp.AllowUnexported(CustomTime{})) {
+				t.Errorf("%s: got %+v, want %+v", test.desc, test.ptr, test.want)
+			}
 		}
 	}
 }