{"id":"64cbfbab1d8030f0","repo":"go-sql-driver/mysql","slug":"non-value-type-t-returned-from-value","errorCode":null,"errorMessage":"non-Value type %T returned from Value","messagePattern":"non-Value type %T returned from Value","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"statement.go","lineNumber":180,"sourceCode":"\tif driver.IsValue(v) {\n\t\treturn v, nil\n\t}\n\n\tif vr, ok := v.(driver.Valuer); ok {\n\t\tsv, err := callValuerValue(vr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif driver.IsValue(sv) {\n\t\t\treturn sv, nil\n\t\t}\n\t\t// A value returned from the Valuer interface can be \"a type handled by\n\t\t// a database driver's NamedValueChecker interface\" so we should accept\n\t\t// uint64 here as well.\n\t\tif u, ok := sv.(uint64); ok {\n\t\t\treturn u, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"non-Value type %T returned from Value\", sv)\n\t}\n\trv := reflect.ValueOf(v)\n\tswitch rv.Kind() {\n\tcase reflect.Ptr:\n\t\t// indirect pointers\n\t\tif rv.IsNil() {\n\t\t\treturn nil, nil\n\t\t} else {\n\t\t\treturn c.ConvertValue(rv.Elem().Interface())\n\t\t}\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\treturn rv.Int(), nil\n\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:\n\t\treturn rv.Uint(), nil\n\tcase reflect.Float32, reflect.Float64:\n\t\treturn rv.Float(), nil\n\tcase reflect.Bool:\n\t\treturn rv.Bool(), nil","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/go-sql-driver/mysql/blob/c426bd93799de0f0e094c8f0582872c529d0ed0a/statement.go#L162-L198","documentation":"In the driver's converter (statement.go:180), a type implementing driver.Valuer returned a value from its Value() method that is not a valid driver.Value (nil, int64, float64, bool, []byte, string, time.Time) nor uint64 (a driver-specific allowance). The contract of driver.Valuer.Value() is to return one of those base types; returning anything else is a bug in the Valuer implementation.","triggerScenarios":"A custom type's Value() returns a plain int (not int64), a float32, a struct, a *big.Int, or any non-base wrapper type; a Valuer that returns another custom type instead of unwrapping it.","commonSituations":"Implementing Value() to return int/uint/float32 directly; returning a nested wrapper type; misunderstanding the driver.Value type set.","solutions":["Make Value() return one of the allowed base types, most commonly int64, float64, string, []byte, or time.Time.","Convert numeric custom types explicitly, e.g. return int64(m), nil.","Return nil, nil to represent SQL NULL."],"exampleFix":"// before\nfunc (m Money) Value() (driver.Value, error) { return int(m), nil }\n\n// after\nfunc (m Money) Value() (driver.Value, error) { return int64(m), nil }","handlingStrategy":"type-guard","validationCode":"func checkValuer(v driver.Valuer) error {\n    val, err := v.Value()\n    if err != nil { return err }\n    switch val.(type) {\n    case nil, int64, uint64, float64, bool, []byte, string, time.Time:\n        return nil\n    }\n    return fmt.Errorf(\"Value() returned unsupported type %T\", val)\n}","typeGuard":"func isDriverValue(v any) bool {\n    switch v.(type) {\n    case nil, int64, uint64, float64, bool, []byte, string, time.Time:\n        return true\n    }\n    return false\n}","tryCatchPattern":"if _, err := stmt.Exec(ctx, customVal); err != nil {\n    if strings.Contains(err.Error(), \"non-Value type\") {\n        // fix the custom type's Value() to return a base driver.Value\n    }\n}","preventionTips":["Always return a base driver.Value from Value().","Convert numeric custom types to int64/uint64 explicitly.","Unit-test Valuer implementations against the driver."],"tags":["types","valuer","conversion","api-misuse"],"analyzedSha":"c426bd93799de0f0e094c8f0582872c529d0ed0a","analyzedAt":"2026-08-04T21:52:59.219Z","schemaVersion":2}