spanner/spannertest: factor out DB->spannerpb type/value transcoding

This simplifies readStream and helps prepare for support for array
types.

Updates #1181.

Change-Id: Iaf7a28f62fb18831ade8cf2459f9240e9388e211
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/43233
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jean de Klerk <deklerk@google.com>
diff --git a/spanner/spannertest/inmem.go b/spanner/spannertest/inmem.go
index c6dceb0..fde8f5a 100644
--- a/spanner/spannertest/inmem.go
+++ b/spanner/spannertest/inmem.go
@@ -474,25 +474,13 @@
 		// TODO: transaction info?
 	}
 	for _, ci := range ri.Cols {
-		// TODO: array types
-		var code spannerpb.TypeCode
-		switch ci.Type.Base {
-		case spansql.Bool:
-			code = spannerpb.TypeCode_BOOL
-		case spansql.Int64:
-			code = spannerpb.TypeCode_INT64
-		case spansql.Float64:
-			code = spannerpb.TypeCode_FLOAT64
-		case spansql.String:
-			code = spannerpb.TypeCode_STRING
-		case spansql.Bytes:
-			code = spannerpb.TypeCode_BYTES
-		default:
-			return fmt.Errorf("unhandled base type %d", ci.Type.Base)
+		st, err := spannerTypeFromType(ci.Type)
+		if err != nil {
+			return err
 		}
 		rsm.RowType.Fields = append(rsm.RowType.Fields, &spannerpb.StructType_Field{
 			Name: ci.Name,
-			Type: &spannerpb.Type{Code: code},
+			Type: st,
 		})
 	}
 
@@ -504,20 +492,11 @@
 
 		values := make([]*structpb.Value, len(row))
 		for i, x := range row {
-			switch x := x.(type) {
-			default:
-				return fmt.Errorf("unhandled database type %T", x)
-			case int64:
-				// The Spanner int64 is actually a decimal string.
-				s := strconv.FormatInt(x, 10)
-				values[i] = &structpb.Value{Kind: &structpb.Value_StringValue{s}}
-			case float64:
-				values[i] = &structpb.Value{Kind: &structpb.Value_NumberValue{x}}
-			case string:
-				values[i] = &structpb.Value{Kind: &structpb.Value_StringValue{x}}
-			case nil:
-				values[i] = &structpb.Value{Kind: &structpb.Value_NullValue{}}
+			v, err := spannerValueFromValue(x)
+			if err != nil {
+				return err
 			}
+			values[i] = v
 		}
 
 		prs := &spannerpb.PartialResultSet{
@@ -626,3 +605,40 @@
 }
 
 // TODO: PartitionQuery, PartitionRead
+
+func spannerTypeFromType(typ spansql.Type) (*spannerpb.Type, error) {
+	// TODO: array types
+	var code spannerpb.TypeCode
+	switch typ.Base {
+	default:
+		return nil, fmt.Errorf("unhandled base type %d", typ.Base)
+	case spansql.Bool:
+		code = spannerpb.TypeCode_BOOL
+	case spansql.Int64:
+		code = spannerpb.TypeCode_INT64
+	case spansql.Float64:
+		code = spannerpb.TypeCode_FLOAT64
+	case spansql.String:
+		code = spannerpb.TypeCode_STRING
+	case spansql.Bytes:
+		code = spannerpb.TypeCode_BYTES
+	}
+	return &spannerpb.Type{Code: code}, nil
+}
+
+func spannerValueFromValue(x interface{}) (*structpb.Value, error) {
+	switch x := x.(type) {
+	default:
+		return nil, fmt.Errorf("unhandled database value type %T", x)
+	case int64:
+		// The Spanner int64 is actually a decimal string.
+		s := strconv.FormatInt(x, 10)
+		return &structpb.Value{Kind: &structpb.Value_StringValue{s}}, nil
+	case float64:
+		return &structpb.Value{Kind: &structpb.Value_NumberValue{x}}, nil
+	case string:
+		return &structpb.Value{Kind: &structpb.Value_StringValue{x}}, nil
+	case nil:
+		return &structpb.Value{Kind: &structpb.Value_NullValue{}}, nil
+	}
+}