Repository: googleapis/google-cloud-go (go)

Issue:
bigquery: big.Rat limited to 9 digits
**Client**

BigQuery

**Environment**

N/A

**Go Environment**

```
go version go1.16.6 darwin/arm64

GO111MODULE=""
GOARCH="arm64"
GOBIN=""
GOCACHE="/Users/thomas/Library/Caches/go-build"
GOENV="/Users/thomas/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/thomas/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/thomas/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/opt/homebrew/Cellar/go/1.16.6/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/opt/homebrew/Cellar/go/1.16.6/libexec/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.16.6"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/lv/qbz3lh7j6gdc43dd1p22bcc40000gn/T/go-build541126118=/tmp/go-build -gno-record-gcc-switches -fno-common"
```

**Code**

https://pkg.go.dev/cloud.google.com/go/bigquery#InferSchema documents that `big.Rat` types are limited to 9 digits after then decimal, this also seems to be the case for parameters to query. However BigQuery supports much more precision (up to 76, https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#numeric-type). It would be nice to able to query using these high precision numbers.

I guess that the parameters are send as strings so you have to make a decision, but could the limit be increased? Alternatively can we send our own string (with our own defined precision) as number?

Also related: https://github.com/golang/go/issues/47911: Support precision of -1 in Rat.FloatString (that will use the required number of digits to represent the number).

**Expected behavior**

I expected large precision numbers to be support.

**Actual behavior**

Large numbers are limited to 9 digits after the decimal.


Pull request:
feat(bigquery): add support for explicit query parameter type
Allow users to pass query parameter with an explicit data types instead of relying just on reflection of Go types. Right now the data type that is affect by this change is `*big.Rat`, which before was only converted to `NUMERIC` with 9 digits, and now can be send as a `BIGNUMERIC`.

This adds two methods:
* `NewScalarQueryParameter(name string, paramType FieldType, value interface{}) QueryParameter`
* `NewArrayQueryParameter(name string, paramType FieldType, value interface{}) QueryParameter`

Example usage:
```
ctx := context.Background()
client, err := bigquery.NewClient(ctx, projectID)
if err != nil {
        return fmt.Errorf("bigquery.NewClient: %v", err)
}
defer client.Close()

rat := big.NewRat(12345, 10e10)
q := client.Query("SELECT @bigval, @array")
q.Parameters = []bigquery.QueryParameter{
        {
                Value: NewScalarQueryParameter("bigval", "BIGNUMERIC", rat)
        },
        {
                Value: NewArrayQueryParameter("array", "STRING", []string{"a","b","c"}
        },
}
```

I'm still evaluating if we need something like `NewStructQueryParameter` to pass a list of QueryParameters with explicit data types for each field. 

Most of this was inspired by the Python implementation that has the same kind of methods for creating query parameters.
> see: https://cloud.google.com/bigquery/docs/parameterized-queries#python_1

Resolves #4704

Patch:
diff --git a/bigquery/params.go b/bigquery/params.go
index 0f780dc308..5cafbf85f0 100644
--- a/bigquery/params.go
+++ b/bigquery/params.go
@@ -82,12 +82,13 @@ var (
 )
 
 var (
-	typeOfDate          = reflect.TypeOf(civil.Date{})
-	typeOfTime          = reflect.TypeOf(civil.Time{})
-	typeOfDateTime      = reflect.TypeOf(civil.DateTime{})
-	typeOfGoTime        = reflect.TypeOf(time.Time{})
-	typeOfRat           = reflect.TypeOf(&big.Rat{})
-	typeOfIntervalValue = reflect.TypeOf(&IntervalValue{})
+	typeOfDate                = reflect.TypeOf(civil.Date{})
+	typeOfTime                = reflect.TypeOf(civil.Time{})
+	typeOfDateTime            = reflect.TypeOf(civil.DateTime{})
+	typeOfGoTime              = reflect.TypeOf(time.Time{})
+	typeOfRat                 = reflect.TypeOf(&big.Rat{})
+	typeOfIntervalValue       = reflect.TypeOf(&IntervalValue{})
+	typeOfQueryParameterValue = reflect.TypeOf(&QueryParameterValue{})
 )
 
 // A QueryParameter is a parameter to a query.
@@ -116,6 +117,15 @@ type QueryParameter struct {
 	// For scalar values, you can supply the Null types within this library
 	// to send the appropriate NULL values (e.g. NullInt64, NullString, etc).
 	//
+	// To specify query parameters explicitly rather by inference, *QueryParameterValue can be used.
+	// For example, a BIGNUMERIC can be specified like this:
+	// &QueryParameterValue{
+	//		Type: StandardSQLDataType{
+	//			TypeKind: "BIGNUMERIC",
+	//		},
+	//		Value: BigNumericString(*big.Rat),
+	//	}
+	//
 	// When a QueryParameter is returned inside a QueryConfig from a call to
 	// Job.Config:
 	// Integers are of type int64.
@@ -129,12 +139,165 @@ type QueryParameter struct {
 	Value interface{}
 }
 
-func (p QueryParameter) toBQ() (*bq.QueryParameter, error) {
+// QueryParameterValue is a go type for representing a explicit typed QueryParameter.
+type QueryParameterValue struct {
+	// Type specifies the parameter type. See StandardSQLDataType for more.
+	// Scalar parameters and more complex types can be defined within this field.
+	// See examples on the value fields.
+	Type StandardSQLDataType
+
+	// Value is the value of the parameter, if a simple scalar type.
+	// The default behavior for scalar values is to do type inference
+	// and format it accordingly.
+	// Because of that, depending on the parameter type, is recommended
+	// to send value as a String.
+	// We provide some formatter functions for some types:
+	//   CivilTimeString(civil.Time)
+	//   CivilDateTimeString(civil.DateTime)
+	//   NumericString(*big.Rat)
+	//   BigNumericString(*big.Rat)
+	//   IntervalString(*IntervalValue)
+	//
+	// Example:
+	//
+	// &QueryParameterValue{
+	// 		Type: StandardSQLDataType{
+	//			TypeKind: "BIGNUMERIC",
+	//		},
+	//		Value: BigNumericString(*big.Rat),
+	//	}
+	Value interface{}
+
+	// ArrayValue is the array of values for the parameter.
+	//
+	// Must be used with QueryParameterValue.Type being a StandardSQLDataType
+	// with ArrayElementType filled with the given element type.
+	//
+	// Example of an array of strings :
+	// &QueryParameterValue{
+	//		Type: &StandardSQLDataType{
+	// 			ArrayElementType: &StandardSQLDataType{
+	//				TypeKind: "STRING",
+	//			},
+	//		},
+	//		ArrayValue: []QueryParameterValue{
+	//			{Value: "a"},
+	//			{Value: "b"},
+	//		},
+	//	}
+	//
+	// Example of an array of structs :
+	// &QueryParameterValue{
+	//		Type: &StandardSQLDataType{
+	// 			ArrayElementType: &StandardSQLDataType{
+	//	 			StructType: &StandardSQLDataType{
+	//					Fields: []*StandardSQLField{
+	//						{
+	//							Name: "NumberField",
+	//							Type: &StandardSQLDataType{
+	//								TypeKind: "INT64",
+	//							},
+	//						},
+	//					},
+	//				},
+	//			},
+	// 		},
+	//		ArrayValue: []QueryParameterValue{
+	//			{StructValue: map[string]QueryParameterValue{
+	//				"NumberField": {
+	//					Value: int64(42),
+	//				},
+	// 			}},
+	// 			{StructValue: map[string]QueryParameterValue{
+	//				"NumberField": {
+	//					Value: int64(43),
+	//				},
+	// 			}},
+	//		},
+	//	}
+	ArrayValue []QueryParameterValue
+
+	// StructValue is the struct field values for the parameter.
+	//
+	// Must be used with QueryParameterValue.Type being a StandardSQLDataType
+	// with StructType filled with the given field types.
+	//
+	// Example:
+	//
+	// &QueryParameterValue{
+	//		Type: &StandardSQLDataType{
+	// 			StructType{
+	//				Fields: []*StandardSQLField{
+	//					{
+	//						Name: "StringField",
+	//						Type: &StandardSQLDataType{
+	//							TypeKind: "STRING",
+	//						},
+	//					},
+	//					{
+	//						Name: "NumberField",
+	//						Type: &StandardSQLDataType{
+	//							TypeKind: "INT64",
+	//						},
+	//					},
+	//				},
+	//			},
+	//		},
+	//		StructValue: []map[string]QueryParameterValue{
+	//			"NumberField": {
+	//				Value: int64(42),
+	//			},
+	//			"StringField": {
+	//				Value: "Value",
+	//			},
+	//		},
+	//	}
+	StructValue map[string]QueryParameterValue
+}
+
+func (p QueryParameterValue) toBQParamType() *bq.QueryParameterType {
+	return p.Type.toBQParamType()
+}
+
+func (p QueryParameterValue) toBQParamValue() (*bq.QueryParameterValue, error) {
+	if len(p.ArrayValue) > 0 {
+		pv := &bq.QueryParameterValue{}
+		pv.ArrayValues = []*bq.QueryParameterValue{}
+		for _, v := range p.ArrayValue {
+			val, err := v.toBQParamValue()
+			if err != nil {
+				return nil, err
+			}
+			pv.ArrayValues = append(pv.ArrayValues, val)
+		}
+		return pv, nil
+	}
+	if len(p.StructValue) > 0 {
+		pv := &bq.QueryParameterValue{}
+		pv.StructValues = map[string]bq.QueryParameterValue{}
+		for name, param := range p.StructValue {
+			v, err := param.toBQParamValue()
+			if err != nil {
+				return nil, err
+			}
+			pv.StructValues[name] = *v
+		}
+		return pv, nil
+	}
 	pv, err := paramValue(reflect.ValueOf(p.Value))
 	if err != nil {
 		return nil, err
 	}
-	pt, err := paramType(reflect.TypeOf(p.Value))
+	return pv, nil
+}
+
+func (p QueryParameter) toBQ() (*bq.QueryParameter, error) {
+	v := reflect.ValueOf(p.Value)
+	pv, err := paramValue(v)
+	if err != nil {
+		return nil, err
+	}
+	pt, err := paramType(reflect.TypeOf(p.Value), v)
 	if err != nil {
 		return nil, err
 	}
@@ -145,7 +308,7 @@ func (p QueryParameter) toBQ() (*bq.QueryParameter, error) {
 	}, nil
 }
 
-func paramType(t reflect.Type) (*bq.QueryParameterType, error) {
+func paramType(t reflect.Type, v reflect.Value) (*bq.QueryParameterType, error) {
 	if t == nil {
 		return nil, errors.New("bigquery: nil parameter")
 	}
@@ -174,6 +337,8 @@ func paramType(t reflect.Type) (*bq.QueryParameterType, error) {
 		return geographyParamType, nil
 	case typeOfNullJSON:
 		return jsonParamType, nil
+	case typeOfQueryParameterValue:
+		return v.Interface().(*QueryParameterValue).toBQParamType(), nil
 	}
 	switch t.Kind() {
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32:
@@ -195,7 +360,7 @@ func paramType(t reflect.Type) (*bq.QueryParameterType, error) {
 		fallthrough
 
 	case reflect.Array:
-		et, err := paramType(t.Elem())
+		et, err := paramType(t.Elem(), v)
 		if err != nil {
 			return nil, err
 		}
@@ -215,7 +380,7 @@ func paramType(t reflect.Type) (*bq.QueryParameterType, error) {
 			return nil, err
 		}
 		for _, f := range fields {
-			pt, err := paramType(f.Type)
+			pt, err := paramType(f.Type, v)
 			if err != nil {
 				return nil, err
 			}
@@ -314,6 +479,8 @@ func paramValue(v reflect.Value) (*bq.QueryParameterValue, error) {
 	case typeOfIntervalValue:
 		res.Value = IntervalString(v.Interface().(*IntervalValue))
 		return res, nil
+	case typeOfQueryParameterValue:
+		return v.Interface().(*QueryParameterValue).toBQParamValue()
 	}
 	switch t.Kind() {
 	case reflect.Slice:
diff --git a/bigquery/standardsql.go b/bigquery/standardsql.go
index 1101bbb065..7f8ca6e116 100644
--- a/bigquery/standardsql.go
+++ b/bigquery/standardsql.go
@@ -63,6 +63,23 @@ func (ssdt *StandardSQLDataType) toBQ() (*bq.StandardSqlDataType, error) {
 	return bqdt, nil
 }
 
+func (ssdt StandardSQLDataType) toBQParamType() *bq.QueryParameterType {
+	if ssdt.ArrayElementType != nil {
+		return &bq.QueryParameterType{Type: "ARRAY", ArrayType: ssdt.ArrayElementType.toBQParamType()}
+	}
+	if ssdt.StructType != nil {
+		var fts []*bq.QueryParameterTypeStructTypes
+		for _, field := range ssdt.StructType.Fields {
+			fts = append(fts, &bq.QueryParameterTypeStructTypes{
+				Name: field.Name,
+				Type: field.Type.toBQParamType(),
+			})
+		}
+		return &bq.QueryParameterType{Type: "STRUCT", StructTypes: fts}
+	}
+	return &bq.QueryParameterType{Type: ssdt.TypeKind}
+}
+
 func bqToStandardSQLDataType(bqdt *bq.StandardSqlDataType) (*StandardSQLDataType, error) {
 	if bqdt == nil {
 		return nil, nil


Test patch:
diff --git a/bigquery/integration_test.go b/bigquery/integration_test.go
index 0d46640685..ba8e3cab09 100644
--- a/bigquery/integration_test.go
+++ b/bigquery/integration_test.go
@@ -1808,6 +1808,7 @@ func TestIntegration_QueryParameters(t *testing.T) {
 	dtm := civil.DateTime{Date: d, Time: tm}
 	ts := time.Date(2016, 3, 20, 15, 04, 05, 0, time.UTC)
 	rat := big.NewRat(13, 10)
+	bigRat := big.NewRat(12345, 10e10)
 
 	type ss struct {
 		String string
@@ -1828,73 +1829,73 @@ func TestIntegration_QueryParameters(t *testing.T) {
 	}{
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", 1}},
+			[]QueryParameter{{Name: "val", Value: 1}},
 			[]Value{int64(1)},
 			int64(1),
 		},
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", 1.3}},
+			[]QueryParameter{{Name: "val", Value: 1.3}},
 			[]Value{1.3},
 			1.3,
 		},
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", rat}},
+			[]QueryParameter{{Name: "val", Value: rat}},
 			[]Value{rat},
 			rat,
 		},
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", true}},
+			[]QueryParameter{{Name: "val", Value: true}},
 			[]Value{true},
 			true,
 		},
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", "ABC"}},
+			[]QueryParameter{{Name: "val", Value: "ABC"}},
 			[]Value{"ABC"},
 			"ABC",
 		},
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", []byte("foo")}},
+			[]QueryParameter{{Name: "val", Value: []byte("foo")}},
 			[]Value{[]byte("foo")},
 			[]byte("foo"),
 		},
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", ts}},
+			[]QueryParameter{{Name: "val", Value: ts}},
 			[]Value{ts},
 			ts,
 		},
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", []time.Time{ts, ts}}},
+			[]QueryParameter{{Name: "val", Value: []time.Time{ts, ts}}},
 			[]Value{[]Value{ts, ts}},
 			[]interface{}{ts, ts},
 		},
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", dtm}},
+			[]QueryParameter{{Name: "val", Value: dtm}},
 			[]Value{civil.DateTime{Date: d, Time: rtm}},
 			civil.DateTime{Date: d, Time: rtm},
 		},
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", d}},
+			[]QueryParameter{{Name: "val", Value: d}},
 			[]Value{d},
 			d,
 		},
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", tm}},
+			[]QueryParameter{{Name: "val", Value: tm}},
 			[]Value{rtm},
 			rtm,
 		},
 		{
 			"SELECT @val",
-			[]QueryParameter{{"val", s{ts, []string{"a", "b"}, ss{"c"}, []ss{{"d"}, {"e"}}}}},
+			[]QueryParameter{{Name: "val", Value: s{ts, []string{"a", "b"}, ss{"c"}, []ss{{"d"}, {"e"}}}}},
 			[]Value{[]Value{ts, []Value{"a", "b"}, []Value{"c"}, []Value{[]Value{"d"}, []Value{"e"}}}},
 			map[string]interface{}{
 				"Timestamp":   ts,
@@ -1908,7 +1909,7 @@ func TestIntegration_QueryParameters(t *testing.T) {
 		},
 		{
 			"SELECT @val.Timestamp, @val.SubStruct.String",
-			[]QueryParameter{{"val", s{Timestamp: ts, SubStruct: ss{"a"}}}},
+			[]QueryParameter{{Name: "val", Value: s{Timestamp: ts, SubStruct: ss{"a"}}}},
 			[]Value{ts, "a"},
 			map[string]interface{}{
 				"Timestamp":      ts,
@@ -1917,6 +1918,147 @@ func TestIntegration_QueryParameters(t *testing.T) {
 				"SubStructArray": nil,
 			},
 		},
+		{
+			"SELECT @val",
+			[]QueryParameter{
+				{
+					Name: "val",
+					Value: &QueryParameterValue{
+						Type: StandardSQLDataType{
+							TypeKind: "BIGNUMERIC",
+						},
+						Value: BigNumericString(bigRat),
+					},
+				},
+			},
+			[]Value{bigRat},
+			bigRat,
+		},
+		{
+			"SELECT @val",
+			[]QueryParameter{
+				{
+					Name: "val",
+					Value: &QueryParameterValue{
+						ArrayValue: []QueryParameterValue{
+							{Value: "a"},
+							{Value: "b"},
+						},
+						Type: StandardSQLDataType{
+							ArrayElementType: &StandardSQLDataType{
+								TypeKind: "STRING",
+							},
+						},
+					},
+				},
+			},
+			[]Value{[]Value{"a", "b"}},
+			[]interface{}{"a", "b"},
+		},
+		{
+			"SELECT @val",
+			[]QueryParameter{
+				{
+					Name: "val",
+					Value: &QueryParameterValue{
+						StructValue: map[string]QueryParameterValue{
+							"Timestamp": {
+								Value: ts,
+							},
+							"BigNumericArray": {
+								ArrayValue: []QueryParameterValue{
+									{Value: BigNumericString(bigRat)},
+									{Value: BigNumericString(rat)},
+								},
+							},
+							"ArraySingleValueStruct": {
+								ArrayValue: []QueryParameterValue{
+									{StructValue: map[string]QueryParameterValue{
+										"Number": {
+											Value: int64(42),
+										},
+									}},
+									{StructValue: map[string]QueryParameterValue{
+										"Number": {
+											Value: int64(43),
+										},
+									}},
+								},
+							},
+							"SubStruct": {
+								StructValue: map[string]QueryParameterValue{
+									"String": {
+										Value: "c",
+									},
+								},
+							},
+						},
+						Type: StandardSQLDataType{
+							StructType: &StandardSQLStructType{
+								Fields: []*StandardSQLField{
+									{
+										Name: "Timestamp",
+										Type: &StandardSQLDataType{
+											TypeKind: "TIMESTAMP",
+										},
+									},
+									{
+										Name: "BigNumericArray",
+										Type: &StandardSQLDataType{
+											ArrayElementType: &StandardSQLDataType{
+												TypeKind: "BIGNUMERIC",
+											},
+										},
+									},
+									{
+										Name: "ArraySingleValueStruct",
+										Type: &StandardSQLDataType{
+											ArrayElementType: &StandardSQLDataType{
+												StructType: &StandardSQLStructType{
+													Fields: []*StandardSQLField{
+														{
+															Name: "Number",
+															Type: &StandardSQLDataType{
+																TypeKind: "INT64",
+															},
+														},
+													},
+												},
+											},
+										},
+									},
+									{
+										Name: "SubStruct",
+										Type: &StandardSQLDataType{
+											StructType: &StandardSQLStructType{
+												Fields: []*StandardSQLField{
+													{
+														Name: "String",
+														Type: &StandardSQLDataType{
+															TypeKind: "STRING",
+														},
+													},
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			[]Value{[]Value{ts, []Value{bigRat, rat}, []Value{[]Value{int64(42)}, []Value{int64(43)}}, []Value{"c"}}},
+			map[string]interface{}{
+				"Timestamp":       ts,
+				"BigNumericArray": []interface{}{bigRat, rat},
+				"ArraySingleValueStruct": []interface{}{
+					map[string]interface{}{"Number": int64(42)},
+					map[string]interface{}{"Number": int64(43)},
+				},
+				"SubStruct": map[string]interface{}{"String": "c"},
+			},
+		},
 	}
 	for _, c := range testCases {
 		q := client.Query(c.query)
diff --git a/bigquery/params_test.go b/bigquery/params_test.go
index 6be55301d0..fa6a293051 100644
--- a/bigquery/params_test.go
+++ b/bigquery/params_test.go
@@ -116,6 +116,12 @@ var scalarTests = []struct {
 		dateTimeParamType,
 		NullDateTime{Valid: false}},
 	{big.NewRat(12345, 1000), false, "12.345000000", numericParamType, big.NewRat(12345, 1000)},
+	{&QueryParameterValue{
+		Type: StandardSQLDataType{
+			TypeKind: "BIGNUMERIC",
+		},
+		Value: BigNumericString(big.NewRat(12345, 10e10)),
+	}, false, "0.00000012345000000000000000000000000000", bigNumericParamType, big.NewRat(12345, 10e10)},
 	{&IntervalValue{Years: 1, Months: 2, Days: 3}, false, "1-2 3 0:0:0", intervalParamType, &IntervalValue{Years: 1, Months: 2, Days: 3}},
 	{NullGeography{GeographyVal: "POINT(-122.335503 47.625536)", Valid: true}, false, "POINT(-122.335503 47.625536)", geographyParamType, "POINT(-122.335503 47.625536)"},
 	{NullGeography{Valid: false}, true, "", geographyParamType, NullGeography{Valid: false}},
@@ -179,7 +185,6 @@ func sval(s string) bq.QueryParameterValue {
 }
 
 func TestParamValueScalar(t *testing.T) {
-
 	nilValue := &bq.QueryParameterValue{
 		NullFields: []string{"Value"},
 	}
@@ -250,7 +255,7 @@ func TestParamValueErrors(t *testing.T) {
 
 func TestParamType(t *testing.T) {
 	for _, test := range scalarTests {
-		got, err := paramType(reflect.TypeOf(test.val))
+		got, err := paramType(reflect.TypeOf(test.val), reflect.ValueOf(test.val))
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -268,7 +273,7 @@ func TestParamType(t *testing.T) {
 		{[3]bool{}, &bq.QueryParameterType{Type: "ARRAY", ArrayType: boolParamType}},
 		{S1{}, s1ParamType},
 	} {
-		got, err := paramType(reflect.TypeOf(test.val))
+		got, err := paramType(reflect.TypeOf(test.val), reflect.ValueOf(test.val))
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -282,7 +287,7 @@ func TestParamTypeErrors(t *testing.T) {
 	for _, val := range []interface{}{
 		nil, uint(0), new([]int), make(chan int),
 	} {
-		_, err := paramType(reflect.TypeOf(val))
+		_, err := paramType(reflect.TypeOf(val), reflect.ValueOf(val))
 		if err == nil {
 			t.Errorf("%v (%T): got nil, want error", val, val)
 		}
@@ -296,7 +301,7 @@ func TestConvertParamValue(t *testing.T) {
 		if err != nil {
 			t.Fatal(err)
 		}
-		ptype, err := paramType(reflect.TypeOf(test.val))
+		ptype, err := paramType(reflect.TypeOf(test.val), reflect.ValueOf(test.val))
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -307,7 +312,6 @@ func TestConvertParamValue(t *testing.T) {
 		if !testutil.Equal(got, test.wantStat) {
 			t.Errorf("%#v: wanted stat as %#v, got %#v", test.val, test.wantStat, got)
 		}
-
 	}
 	// Arrays.
 	for _, test := range []struct {