Add all int and float null types

* Add Scan and Value methods for non sql.Int64 and sql.Float64 types
* Change NullInt to use int in the code opposed to int64
* Rename Float to Float64
* Add support for all missing key int and float types:
  float32, int8, int16, int32, int64, uint8, uint16, uint32, uint64

* Update README to include new additions/changes
This commit is contained in:
Patrick O'brien 2016-05-17 22:02:36 +10:00
parent 41961cea03
commit bbb7b84bb6
27 changed files with 3903 additions and 259 deletions

View file

@ -3,13 +3,13 @@
null is a library with reasonable options for dealing with nullable SQL and JSON values
There are two packages: `null` and its subpackage `zero`.
There are two packages: `null` and its subpackage `zero`.
Types in `null` will only be considered null on null input, and will JSON encode to `null`. If you need zero and null be considered separate values, use these.
Types in `zero` are treated like zero values in Go: blank string input will produce a null `zero.String`, and null Strings will JSON encode to `""`. Zero values of these types will be considered null to SQL. If you need zero and null treated the same, use these.
All types implement `sql.Scanner` and `driver.Valuer`, so you can use this library in place of `sql.NullXXX`. All types also implement: `encoding.TextMarshaler`, `encoding.TextUnmarshaler`, `json.Marshaler`, and `json.Unmarshaler`.
All types implement `sql.Scanner` and `driver.Valuer`, so you can use this library in place of `sql.NullXXX`. All types also implement: `encoding.TextMarshaler`, `encoding.TextUnmarshaler`, `json.Marshaler`, and `json.Unmarshaler`.
### null package
@ -18,27 +18,77 @@ All types implement `sql.Scanner` and `driver.Valuer`, so you can use this libra
#### null.String
Nullable string.
Marshals to JSON null if SQL source data is null. Zero (blank) input will not produce a null String. Can unmarshal from `sql.NullString` JSON input or string input.
#### null.Int
Nullable int64.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Int. Can unmarshal from `sql.NullInt64` JSON input.
#### null.Float
Nullable float64.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Float. Can unmarshal from `sql.NullFloat64` JSON input.
Marshals to JSON null if SQL source data is null. Zero (blank) input will not produce a null String. Can unmarshal from `sql.NullString` JSON input or string input.
#### null.Bool
Nullable bool.
Nullable bool.
Marshals to JSON null if SQL source data is null. False input will not produce a null Bool. Can unmarshal from `sql.NullBool` JSON input.
Marshals to JSON null if SQL source data is null. False input will not produce a null Bool. Can unmarshal from `sql.NullBool` JSON input.
#### null.Time
Marshals to JSON null if SQL source data is null. Uses `time.Time`'s marshaler. Can unmarshal from `pq.NullTime` and similar JSON input.
#### null.Float32
Nullable float32.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Float32. Can unmarshal from `null.NullFloat32` JSON input.
#### null.Float64
Nullable float64.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Float64. Can unmarshal from `sql.NullFloat64` JSON input.
#### null.Int
Nullable int.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Int. Can unmarshal from `null.NullInt` JSON input.
#### null.Int8
Nullable int8.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Int8. Can unmarshal from `null.NullInt8` JSON input.
#### null.Int16
Nullable int16.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Int16. Can unmarshal from `null.NullInt16` JSON input.
#### null.Int32
Nullable int32.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Int32. Can unmarshal from `null.NullInt32` JSON input.
#### null.Int64
Nullable int64.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Int64. Can unmarshal from `sql.NullInt64` JSON input.
#### null.Uint
Nullable uint.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Uint. Can unmarshal from `null.NullUint` JSON input.
#### null.Uint8
Nullable uint8.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Uint8. Can unmarshal from `null.NullUint8` JSON input.
#### null.Uint16
Nullable uint16.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Uint16. Can unmarshal from `null.NullUint16` JSON input.
#### null.Uint32
Nullable int32.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Uint32. Can unmarshal from `null.NullUint32` JSON input.
#### null.Int64
Nullable uint64.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Uint64. Can unmarshal from `null.NullUint64` JSON input.
### zero package
`import "gopkg.in/guregu/null.v3/zero"`
@ -46,22 +96,22 @@ Marshals to JSON null if SQL source data is null. Uses `time.Time`'s marshaler.
#### zero.String
Nullable string.
Will marshal to a blank string if null. Blank string input produces a null String. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullString` JSON input.
Will marshal to a blank string if null. Blank string input produces a null String. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullString` JSON input.
#### zero.Int
Nullable int64.
Will marshal to 0 if null. 0 produces a null Int. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullInt64` JSON input.
Will marshal to 0 if null. 0 produces a null Int. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullInt64` JSON input.
#### zero.Float
Nullable float64.
Will marshal to 0 if null. 0.0 produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullFloat64` JSON input.
Will marshal to 0 if null. 0.0 produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullFloat64` JSON input.
#### zero.Bool
Nullable bool.
Will marshal to false if null. `false` produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullBool` JSON input.
Will marshal to false if null. `false` produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullBool` JSON input.
#### zero.Time

266
convert.go Normal file
View file

@ -0,0 +1,266 @@
package null
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Type conversions for Scan.
// These functions are copied from database/sql/convert.go build 1.6.2
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"reflect"
"strconv"
"time"
)
var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error
// convertAssign copies to dest the value in src, converting it if possible.
// An error is returned if the copy would result in loss of information.
// dest should be a pointer type.
func convertAssign(dest, src interface{}) error {
// Common cases, without reflect.
switch s := src.(type) {
case string:
switch d := dest.(type) {
case *string:
if d == nil {
return errNilPtr
}
*d = s
return nil
case *[]byte:
if d == nil {
return errNilPtr
}
*d = []byte(s)
return nil
}
case []byte:
switch d := dest.(type) {
case *string:
if d == nil {
return errNilPtr
}
*d = string(s)
return nil
case *interface{}:
if d == nil {
return errNilPtr
}
*d = cloneBytes(s)
return nil
case *[]byte:
if d == nil {
return errNilPtr
}
*d = cloneBytes(s)
return nil
case *sql.RawBytes:
if d == nil {
return errNilPtr
}
*d = s
return nil
}
case time.Time:
switch d := dest.(type) {
case *string:
*d = s.Format(time.RFC3339Nano)
return nil
case *[]byte:
if d == nil {
return errNilPtr
}
*d = []byte(s.Format(time.RFC3339Nano))
return nil
}
case nil:
switch d := dest.(type) {
case *interface{}:
if d == nil {
return errNilPtr
}
*d = nil
return nil
case *[]byte:
if d == nil {
return errNilPtr
}
*d = nil
return nil
case *sql.RawBytes:
if d == nil {
return errNilPtr
}
*d = nil
return nil
}
}
var sv reflect.Value
switch d := dest.(type) {
case *string:
sv = reflect.ValueOf(src)
switch sv.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
*d = asString(src)
return nil
}
case *[]byte:
sv = reflect.ValueOf(src)
if b, ok := asBytes(nil, sv); ok {
*d = b
return nil
}
case *sql.RawBytes:
sv = reflect.ValueOf(src)
if b, ok := asBytes([]byte(*d)[:0], sv); ok {
*d = sql.RawBytes(b)
return nil
}
case *bool:
bv, err := driver.Bool.ConvertValue(src)
if err == nil {
*d = bv.(bool)
}
return err
case *interface{}:
*d = src
return nil
}
if scanner, ok := dest.(sql.Scanner); ok {
return scanner.Scan(src)
}
dpv := reflect.ValueOf(dest)
if dpv.Kind() != reflect.Ptr {
return errors.New("destination not a pointer")
}
if dpv.IsNil() {
return errNilPtr
}
if !sv.IsValid() {
sv = reflect.ValueOf(src)
}
dv := reflect.Indirect(dpv)
if sv.IsValid() && sv.Type().AssignableTo(dv.Type()) {
dv.Set(sv)
return nil
}
if dv.Kind() == sv.Kind() && sv.Type().ConvertibleTo(dv.Type()) {
dv.Set(sv.Convert(dv.Type()))
return nil
}
switch dv.Kind() {
case reflect.Ptr:
if src == nil {
dv.Set(reflect.Zero(dv.Type()))
return nil
} else {
dv.Set(reflect.New(dv.Type().Elem()))
return convertAssign(dv.Interface(), src)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
s := asString(src)
i64, err := strconv.ParseInt(s, 10, dv.Type().Bits())
if err != nil {
err = strconvErr(err)
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
}
dv.SetInt(i64)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
s := asString(src)
u64, err := strconv.ParseUint(s, 10, dv.Type().Bits())
if err != nil {
err = strconvErr(err)
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
}
dv.SetUint(u64)
return nil
case reflect.Float32, reflect.Float64:
s := asString(src)
f64, err := strconv.ParseFloat(s, dv.Type().Bits())
if err != nil {
err = strconvErr(err)
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
}
dv.SetFloat(f64)
return nil
}
return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest)
}
func strconvErr(err error) error {
if ne, ok := err.(*strconv.NumError); ok {
return ne.Err
}
return err
}
func cloneBytes(b []byte) []byte {
if b == nil {
return nil
} else {
c := make([]byte, len(b))
copy(c, b)
return c
}
}
func asString(src interface{}) string {
switch v := src.(type) {
case string:
return v
case []byte:
return string(v)
}
rv := reflect.ValueOf(src)
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(rv.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(rv.Uint(), 10)
case reflect.Float64:
return strconv.FormatFloat(rv.Float(), 'g', -1, 64)
case reflect.Float32:
return strconv.FormatFloat(rv.Float(), 'g', -1, 32)
case reflect.Bool:
return strconv.FormatBool(rv.Bool())
}
return fmt.Sprintf("%v", src)
}
func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) {
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.AppendInt(buf, rv.Int(), 10), true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.AppendUint(buf, rv.Uint(), 10), true
case reflect.Float32:
return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 32), true
case reflect.Float64:
return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 64), true
case reflect.Bool:
return strconv.AppendBool(buf, rv.Bool()), true
case reflect.String:
s := rv.String()
return append(buf, s...), true
}
return
}

144
float32.go Normal file
View file

@ -0,0 +1,144 @@
package null
import (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// NullFloat32 is a replica of sql.NullFloat64 for float32 types.
type NullFloat32 struct {
Float32 float32
Valid bool
}
// Float32 is a nullable float32.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Float32 struct {
NullFloat32
}
// NewFloat32 creates a new Float32
func NewFloat32(f float32, valid bool) Float32 {
return Float32{
NullFloat32: NullFloat32{
Float32: f,
Valid: valid,
},
}
}
// Float32From creates a new Float32 that will always be valid.
func Float32From(f float32) Float32 {
return NewFloat32(f, true)
}
// Float32FromPtr creates a new Float32 that be null if f is nil.
func Float32FromPtr(f *float32) Float32 {
if f == nil {
return NewFloat32(0, false)
}
return NewFloat32(*f, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Float32.
// It also supports unmarshalling a sql.NullFloat32.
func (f *Float32) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch x := v.(type) {
case float64:
f.Float32 = float32(x)
case map[string]interface{}:
err = json.Unmarshal(data, &f.NullFloat32)
case nil:
f.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Float32", reflect.TypeOf(v).Name())
}
f.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Float32 if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (f *Float32) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
f.Valid = false
return nil
}
var err error
res, err := strconv.ParseFloat(string(text), 32)
f.Valid = err == nil
if f.Valid {
f.Float32 = float32(res)
}
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Float32 is null.
func (f Float32) MarshalJSON() ([]byte, error) {
if !f.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatFloat(float64(f.Float32), 'f', -1, 32)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Float32 is null.
func (f Float32) MarshalText() ([]byte, error) {
if !f.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatFloat(float64(f.Float32), 'f', -1, 32)), nil
}
// SetValid changes this Float32's value and also sets it to be non-null.
func (f *Float32) SetValid(n float32) {
f.Float32 = n
f.Valid = true
}
// Ptr returns a pointer to this Float32's value, or a nil pointer if this Float32 is null.
func (f Float32) Ptr() *float32 {
if !f.Valid {
return nil
}
return &f.Float32
}
// IsZero returns true for invalid Float32s, for future omitempty support (Go 1.4?)
// A non-null Float32 with a 0 value will not be considered zero.
func (f Float32) IsZero() bool {
return !f.Valid
}
// Scan implements the Scanner interface.
func (n *NullFloat32) Scan(value interface{}) error {
if value == nil {
n.Float32, n.Valid = 0, false
return nil
}
n.Valid = true
return convertAssign(&n.Float32, value)
}
// Value implements the driver Valuer interface.
func (n NullFloat32) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Float32, nil
}

169
float32_test.go Normal file
View file

@ -0,0 +1,169 @@
package null
import (
"encoding/json"
"testing"
)
var (
float32JSON = []byte(`1.2345`)
nullFloat32JSON = []byte(`{"Float32":1.2345,"Valid":true}`)
)
func TestFloat32From(t *testing.T) {
f := Float32From(1.2345)
assertFloat32(t, f, "Float32From()")
zero := Float32From(0)
if !zero.Valid {
t.Error("Float32From(0)", "is invalid, but should be valid")
}
}
func TestFloat32FromPtr(t *testing.T) {
n := float32(1.2345)
iptr := &n
f := Float32FromPtr(iptr)
assertFloat32(t, f, "Float32FromPtr()")
null := Float32FromPtr(nil)
assertNullFloat32(t, null, "Float32FromPtr(nil)")
}
func TestUnmarshalFloat32(t *testing.T) {
var f Float32
err := json.Unmarshal(float32JSON, &f)
maybePanic(err)
assertFloat32(t, f, "float32 json")
var nf Float32
err = json.Unmarshal(nullFloat32JSON, &nf)
maybePanic(err)
assertFloat32(t, nf, "sq.NullFloat32 json")
var null Float32
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullFloat32(t, null, "null json")
var badType Float32
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullFloat32(t, badType, "wrong type json")
var invalid Float32
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
}
func TestTextUnmarshalFloat32(t *testing.T) {
var f Float32
err := f.UnmarshalText([]byte("1.2345"))
maybePanic(err)
assertFloat32(t, f, "UnmarshalText() float32")
var blank Float32
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullFloat32(t, blank, "UnmarshalText() empty float32")
var null Float32
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullFloat32(t, null, `UnmarshalText() "null"`)
}
func TestMarshalFloat32(t *testing.T) {
f := Float32From(1.2345)
data, err := json.Marshal(f)
maybePanic(err)
assertJSONEquals(t, data, "1.2345", "non-empty json marshal")
// invalid values should be encoded as null
null := NewFloat32(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalFloat32Text(t *testing.T) {
f := Float32From(1.2345)
data, err := f.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "1.2345", "non-empty text marshal")
// invalid values should be encoded as null
null := NewFloat32(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestFloat32Pointer(t *testing.T) {
f := Float32From(1.2345)
ptr := f.Ptr()
if *ptr != 1.2345 {
t.Errorf("bad %s float32: %#v ≠ %v\n", "pointer", ptr, 1.2345)
}
null := NewFloat32(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s float32: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestFloat32IsZero(t *testing.T) {
f := Float32From(1.2345)
if f.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewFloat32(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewFloat32(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestFloat32SetValid(t *testing.T) {
change := NewFloat32(0, false)
assertNullFloat32(t, change, "SetValid()")
change.SetValid(1.2345)
assertFloat32(t, change, "SetValid()")
}
func TestFloat32Scan(t *testing.T) {
var f Float32
err := f.Scan(1.2345)
maybePanic(err)
assertFloat32(t, f, "scanned float32")
var null Float32
err = null.Scan(nil)
maybePanic(err)
assertNullFloat32(t, null, "scanned null")
}
func assertFloat32(t *testing.T, f Float32, from string) {
if f.Float32 != 1.2345 {
t.Errorf("bad %s float32: %f ≠ %f\n", from, f.Float32, 1.2345)
}
if !f.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullFloat32(t *testing.T, f Float32, from string) {
if f.Valid {
t.Error(from, "is valid, but should be invalid")
}
}

View file

@ -8,16 +8,16 @@ import (
"strconv"
)
// Float is a nullable float64.
// Float64 is a nullable float64.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Float struct {
type Float64 struct {
sql.NullFloat64
}
// NewFloat creates a new Float
func NewFloat(f float64, valid bool) Float {
return Float{
// NewFloat64 creates a new Float64
func NewFloat64(f float64, valid bool) Float64 {
return Float64{
NullFloat64: sql.NullFloat64{
Float64: f,
Valid: valid,
@ -25,24 +25,24 @@ func NewFloat(f float64, valid bool) Float {
}
}
// FloatFrom creates a new Float that will always be valid.
func FloatFrom(f float64) Float {
return NewFloat(f, true)
// Float64From creates a new Float64 that will always be valid.
func Float64From(f float64) Float64 {
return NewFloat64(f, true)
}
// FloatFromPtr creates a new Float that be null if f is nil.
func FloatFromPtr(f *float64) Float {
// Float64FromPtr creates a new Float64 that be null if f is nil.
func Float64FromPtr(f *float64) Float64 {
if f == nil {
return NewFloat(0, false)
return NewFloat64(0, false)
}
return NewFloat(*f, true)
return NewFloat64(*f, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Float.
// 0 will not be considered a null Float64.
// It also supports unmarshalling a sql.NullFloat64.
func (f *Float) UnmarshalJSON(data []byte) error {
func (f *Float64) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
@ -57,16 +57,16 @@ func (f *Float) UnmarshalJSON(data []byte) error {
f.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Float", reflect.TypeOf(v).Name())
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Float64", reflect.TypeOf(v).Name())
}
f.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Float if the input is a blank or not an integer.
// It will unmarshal to a null Float64 if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (f *Float) UnmarshalText(text []byte) error {
func (f *Float64) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
f.Valid = false
@ -79,8 +79,8 @@ func (f *Float) UnmarshalText(text []byte) error {
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Float is null.
func (f Float) MarshalJSON() ([]byte, error) {
// It will encode null if this Float64 is null.
func (f Float64) MarshalJSON() ([]byte, error) {
if !f.Valid {
return []byte("null"), nil
}
@ -88,30 +88,30 @@ func (f Float) MarshalJSON() ([]byte, error) {
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Float is null.
func (f Float) MarshalText() ([]byte, error) {
// It will encode a blank string if this Float64 is null.
func (f Float64) MarshalText() ([]byte, error) {
if !f.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil
}
// SetValid changes this Float's value and also sets it to be non-null.
func (f *Float) SetValid(n float64) {
// SetValid changes this Float64's value and also sets it to be non-null.
func (f *Float64) SetValid(n float64) {
f.Float64 = n
f.Valid = true
}
// Ptr returns a pointer to this Float's value, or a nil pointer if this Float is null.
func (f Float) Ptr() *float64 {
// Ptr returns a pointer to this Float64's value, or a nil pointer if this Float64 is null.
func (f Float64) Ptr() *float64 {
if !f.Valid {
return nil
}
return &f.Float64
}
// IsZero returns true for invalid Floats, for future omitempty support (Go 1.4?)
// A non-null Float with a 0 value will not be considered zero.
func (f Float) IsZero() bool {
// IsZero returns true for invalid Float64s, for future omitempty support (Go 1.4?)
// A non-null Float64 with a 0 value will not be considered zero.
func (f Float64) IsZero() bool {
return !f.Valid
}

169
float64_test.go Normal file
View file

@ -0,0 +1,169 @@
package null
import (
"encoding/json"
"testing"
)
var (
float64JSON = []byte(`1.2345`)
nullFloat64JSON = []byte(`{"Float64":1.2345,"Valid":true}`)
)
func TestFloat64From(t *testing.T) {
f := Float64From(1.2345)
assertFloat64(t, f, "Float64From()")
zero := Float64From(0)
if !zero.Valid {
t.Error("Float64From(0)", "is invalid, but should be valid")
}
}
func TestFloat64FromPtr(t *testing.T) {
n := float64(1.2345)
iptr := &n
f := Float64FromPtr(iptr)
assertFloat64(t, f, "Float64FromPtr()")
null := Float64FromPtr(nil)
assertNullFloat64(t, null, "Float64FromPtr(nil)")
}
func TestUnmarshalFloat64(t *testing.T) {
var f Float64
err := json.Unmarshal(float64JSON, &f)
maybePanic(err)
assertFloat64(t, f, "float64 json")
var nf Float64
err = json.Unmarshal(nullFloat64JSON, &nf)
maybePanic(err)
assertFloat64(t, nf, "sq.NullFloat64 json")
var null Float64
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullFloat64(t, null, "null json")
var badType Float64
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullFloat64(t, badType, "wrong type json")
var invalid Float64
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
}
func TestTextUnmarshalFloat64(t *testing.T) {
var f Float64
err := f.UnmarshalText([]byte("1.2345"))
maybePanic(err)
assertFloat64(t, f, "UnmarshalText() float64")
var blank Float64
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullFloat64(t, blank, "UnmarshalText() empty float64")
var null Float64
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullFloat64(t, null, `UnmarshalText() "null"`)
}
func TestMarshalFloat64(t *testing.T) {
f := Float64From(1.2345)
data, err := json.Marshal(f)
maybePanic(err)
assertJSONEquals(t, data, "1.2345", "non-empty json marshal")
// invalid values should be encoded as null
null := NewFloat64(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalFloat64Text(t *testing.T) {
f := Float64From(1.2345)
data, err := f.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "1.2345", "non-empty text marshal")
// invalid values should be encoded as null
null := NewFloat64(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestFloat64Pointer(t *testing.T) {
f := Float64From(1.2345)
ptr := f.Ptr()
if *ptr != 1.2345 {
t.Errorf("bad %s float64: %#v ≠ %v\n", "pointer", ptr, 1.2345)
}
null := NewFloat64(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s float64: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestFloat64IsZero(t *testing.T) {
f := Float64From(1.2345)
if f.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewFloat64(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewFloat64(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestFloat64SetValid(t *testing.T) {
change := NewFloat64(0, false)
assertNullFloat64(t, change, "SetValid()")
change.SetValid(1.2345)
assertFloat64(t, change, "SetValid()")
}
func TestFloat64Scan(t *testing.T) {
var f Float64
err := f.Scan(1.2345)
maybePanic(err)
assertFloat64(t, f, "scanned float64")
var null Float64
err = null.Scan(nil)
maybePanic(err)
assertNullFloat64(t, null, "scanned null")
}
func assertFloat64(t *testing.T, f Float64, from string) {
if f.Float64 != 1.2345 {
t.Errorf("bad %s float64: %f ≠ %f\n", from, f.Float64, 1.2345)
}
if !f.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullFloat64(t *testing.T, f Float64, from string) {
if f.Valid {
t.Error(from, "is valid, but should be invalid")
}
}

View file

@ -1,169 +0,0 @@
package null
import (
"encoding/json"
"testing"
)
var (
floatJSON = []byte(`1.2345`)
nullFloatJSON = []byte(`{"Float64":1.2345,"Valid":true}`)
)
func TestFloatFrom(t *testing.T) {
f := FloatFrom(1.2345)
assertFloat(t, f, "FloatFrom()")
zero := FloatFrom(0)
if !zero.Valid {
t.Error("FloatFrom(0)", "is invalid, but should be valid")
}
}
func TestFloatFromPtr(t *testing.T) {
n := float64(1.2345)
iptr := &n
f := FloatFromPtr(iptr)
assertFloat(t, f, "FloatFromPtr()")
null := FloatFromPtr(nil)
assertNullFloat(t, null, "FloatFromPtr(nil)")
}
func TestUnmarshalFloat(t *testing.T) {
var f Float
err := json.Unmarshal(floatJSON, &f)
maybePanic(err)
assertFloat(t, f, "float json")
var nf Float
err = json.Unmarshal(nullFloatJSON, &nf)
maybePanic(err)
assertFloat(t, nf, "sq.NullFloat64 json")
var null Float
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullFloat(t, null, "null json")
var badType Float
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullFloat(t, badType, "wrong type json")
var invalid Float
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
}
func TestTextUnmarshalFloat(t *testing.T) {
var f Float
err := f.UnmarshalText([]byte("1.2345"))
maybePanic(err)
assertFloat(t, f, "UnmarshalText() float")
var blank Float
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullFloat(t, blank, "UnmarshalText() empty float")
var null Float
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullFloat(t, null, `UnmarshalText() "null"`)
}
func TestMarshalFloat(t *testing.T) {
f := FloatFrom(1.2345)
data, err := json.Marshal(f)
maybePanic(err)
assertJSONEquals(t, data, "1.2345", "non-empty json marshal")
// invalid values should be encoded as null
null := NewFloat(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalFloatText(t *testing.T) {
f := FloatFrom(1.2345)
data, err := f.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "1.2345", "non-empty text marshal")
// invalid values should be encoded as null
null := NewFloat(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestFloatPointer(t *testing.T) {
f := FloatFrom(1.2345)
ptr := f.Ptr()
if *ptr != 1.2345 {
t.Errorf("bad %s float: %#v ≠ %v\n", "pointer", ptr, 1.2345)
}
null := NewFloat(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s float: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestFloatIsZero(t *testing.T) {
f := FloatFrom(1.2345)
if f.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewFloat(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewFloat(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestFloatSetValid(t *testing.T) {
change := NewFloat(0, false)
assertNullFloat(t, change, "SetValid()")
change.SetValid(1.2345)
assertFloat(t, change, "SetValid()")
}
func TestFloatScan(t *testing.T) {
var f Float
err := f.Scan(1.2345)
maybePanic(err)
assertFloat(t, f, "scanned float")
var null Float
err = null.Scan(nil)
maybePanic(err)
assertNullFloat(t, null, "scanned null")
}
func assertFloat(t *testing.T, f Float, from string) {
if f.Float64 != 1.2345 {
t.Errorf("bad %s float: %f ≠ %f\n", from, f.Float64, 1.2345)
}
if !f.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullFloat(t *testing.T, f Float, from string) {
if f.Valid {
t.Error(from, "is valid, but should be invalid")
}
}

65
int.go
View file

@ -1,37 +1,43 @@
package null
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// Int is an nullable int64.
// NullInt is a replica of sql.NullInt64 for int types.
type NullInt struct {
Int int
Valid bool
}
// Int is an nullable int.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Int struct {
sql.NullInt64
NullInt
}
// NewInt creates a new Int
func NewInt(i int64, valid bool) Int {
func NewInt(i int, valid bool) Int {
return Int{
NullInt64: sql.NullInt64{
Int64: i,
NullInt: NullInt{
Int: i,
Valid: valid,
},
}
}
// IntFrom creates a new Int that will always be valid.
func IntFrom(i int64) Int {
func IntFrom(i int) Int {
return NewInt(i, true)
}
// IntFromPtr creates a new Int that be null if i is nil.
func IntFromPtr(i *int64) Int {
func IntFromPtr(i *int) Int {
if i == nil {
return NewInt(0, false)
}
@ -41,7 +47,7 @@ func IntFromPtr(i *int64) Int {
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Int.
// It also supports unmarshalling a sql.NullInt64.
// It also supports unmarshalling a sql.NullInt.
func (i *Int) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
@ -50,10 +56,10 @@ func (i *Int) UnmarshalJSON(data []byte) error {
}
switch v.(type) {
case float64:
// Unmarshal again, directly to int64, to avoid intermediate float64
err = json.Unmarshal(data, &i.Int64)
// Unmarshal again, directly to int, to avoid intermediate float64
err = json.Unmarshal(data, &i.Int)
case map[string]interface{}:
err = json.Unmarshal(data, &i.NullInt64)
err = json.Unmarshal(data, &i.NullInt)
case nil:
i.Valid = false
return nil
@ -74,8 +80,11 @@ func (i *Int) UnmarshalText(text []byte) error {
return nil
}
var err error
i.Int64, err = strconv.ParseInt(string(text), 10, 64)
res, err := strconv.ParseInt(string(text), 10, 0)
i.Valid = err == nil
if i.Valid {
i.Int = int(res)
}
return err
}
@ -85,7 +94,7 @@ func (i Int) MarshalJSON() ([]byte, error) {
if !i.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatInt(i.Int64, 10)), nil
return []byte(strconv.FormatInt(int64(i.Int), 10)), nil
}
// MarshalText implements encoding.TextMarshaler.
@ -94,21 +103,21 @@ func (i Int) MarshalText() ([]byte, error) {
if !i.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatInt(i.Int64, 10)), nil
return []byte(strconv.FormatInt(int64(i.Int), 10)), nil
}
// SetValid changes this Int's value and also sets it to be non-null.
func (i *Int) SetValid(n int64) {
i.Int64 = n
func (i *Int) SetValid(n int) {
i.Int = n
i.Valid = true
}
// Ptr returns a pointer to this Int's value, or a nil pointer if this Int is null.
func (i Int) Ptr() *int64 {
func (i Int) Ptr() *int {
if !i.Valid {
return nil
}
return &i.Int64
return &i.Int
}
// IsZero returns true for invalid Ints, for future omitempty support (Go 1.4?)
@ -116,3 +125,21 @@ func (i Int) Ptr() *int64 {
func (i Int) IsZero() bool {
return !i.Valid
}
// Scan implements the Scanner interface.
func (n *NullInt) Scan(value interface{}) error {
if value == nil {
n.Int, n.Valid = 0, false
return nil
}
n.Valid = true
return convertAssign(&n.Int, value)
}
// Value implements the driver Valuer interface.
func (n NullInt) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Int, nil
}

145
int16.go Normal file
View file

@ -0,0 +1,145 @@
package null
import (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// NullInt16 is a replica of sql.NullInt64 for int16 types.
type NullInt16 struct {
Int16 int16
Valid bool
}
// Int16 is an nullable int16.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Int16 struct {
NullInt16
}
// NewInt16 creates a new Int16
func NewInt16(i int16, valid bool) Int16 {
return Int16{
NullInt16: NullInt16{
Int16: i,
Valid: valid,
},
}
}
// Int16From creates a new Int16 that will always be valid.
func Int16From(i int16) Int16 {
return NewInt16(i, true)
}
// Int16FromPtr creates a new Int16 that be null if i is nil.
func Int16FromPtr(i *int16) Int16 {
if i == nil {
return NewInt16(0, false)
}
return NewInt16(*i, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Int16.
// It also supports unmarshalling a sql.NullInt16.
func (i *Int16) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch v.(type) {
case float64:
// Unmarshal again, directly to int16, to avoid intermediate float64
err = json.Unmarshal(data, &i.Int16)
case map[string]interface{}:
err = json.Unmarshal(data, &i.NullInt16)
case nil:
i.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Int16", reflect.TypeOf(v).Name())
}
i.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Int16 if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (i *Int16) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
i.Valid = false
return nil
}
var err error
res, err := strconv.ParseInt(string(text), 10, 16)
i.Valid = err == nil
if i.Valid {
i.Int16 = int16(res)
}
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Int16 is null.
func (i Int16) MarshalJSON() ([]byte, error) {
if !i.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatInt(int64(i.Int16), 10)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Int16 is null.
func (i Int16) MarshalText() ([]byte, error) {
if !i.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatInt(int64(i.Int16), 10)), nil
}
// SetValid changes this Int16's value and also sets it to be non-null.
func (i *Int16) SetValid(n int16) {
i.Int16 = n
i.Valid = true
}
// Ptr returns a pointer to this Int16's value, or a nil pointer if this Int16 is null.
func (i Int16) Ptr() *int16 {
if !i.Valid {
return nil
}
return &i.Int16
}
// IsZero returns true for invalid Int16's, for future omitempty support (Go 1.4?)
// A non-null Int16 with a 0 value will not be considered zero.
func (i Int16) IsZero() bool {
return !i.Valid
}
// Scan implements the Scanner interface.
func (n *NullInt16) Scan(value interface{}) error {
if value == nil {
n.Int16, n.Valid = 0, false
return nil
}
n.Valid = true
return convertAssign(&n.Int16, value)
}
// Value implements the driver Valuer interface.
func (n NullInt16) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Int16, nil
}

196
int16_test.go Normal file
View file

@ -0,0 +1,196 @@
package null
import (
"encoding/json"
"math"
"strconv"
"testing"
)
var (
int16JSON = []byte(`32766`)
nullInt16JSON = []byte(`{"Int16":32766,"Valid":true}`)
)
func TestInt16From(t *testing.T) {
i := Int16From(32766)
assertInt16(t, i, "Int16From()")
zero := Int16From(0)
if !zero.Valid {
t.Error("Int16From(0)", "is invalid, but should be valid")
}
}
func TestInt16FromPtr(t *testing.T) {
n := int16(32766)
iptr := &n
i := Int16FromPtr(iptr)
assertInt16(t, i, "Int16FromPtr()")
null := Int16FromPtr(nil)
assertNullInt16(t, null, "Int16FromPtr(nil)")
}
func TestUnmarshalInt16(t *testing.T) {
var i Int16
err := json.Unmarshal(int16JSON, &i)
maybePanic(err)
assertInt16(t, i, "int16 json")
var ni Int16
err = json.Unmarshal(nullInt16JSON, &ni)
maybePanic(err)
assertInt16(t, ni, "sq.NullInt16 json")
var null Int16
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullInt16(t, null, "null json")
var badType Int16
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullInt16(t, badType, "wrong type json")
var invalid Int16
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
assertNullInt16(t, invalid, "invalid json")
}
func TestUnmarshalNonIntegerNumber16(t *testing.T) {
var i Int16
err := json.Unmarshal(float64JSON, &i)
if err == nil {
panic("err should be present; non-integer number coerced to int16")
}
}
func TestUnmarshalInt16Overflow(t *testing.T) {
int16Overflow := uint16(math.MaxInt16)
// Max int16 should decode successfully
var i Int16
err := json.Unmarshal([]byte(strconv.FormatUint(uint64(int16Overflow), 10)), &i)
maybePanic(err)
// Attempt to overflow
int16Overflow++
err = json.Unmarshal([]byte(strconv.FormatUint(uint64(int16Overflow), 10)), &i)
if err == nil {
panic("err should be present; decoded value overflows int16")
}
}
func TestTextUnmarshalInt16(t *testing.T) {
var i Int16
err := i.UnmarshalText([]byte("32766"))
maybePanic(err)
assertInt16(t, i, "UnmarshalText() int16")
var blank Int16
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullInt16(t, blank, "UnmarshalText() empty int16")
var null Int16
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullInt16(t, null, `UnmarshalText() "null"`)
}
func TestMarshalInt16(t *testing.T) {
i := Int16From(32766)
data, err := json.Marshal(i)
maybePanic(err)
assertJSONEquals(t, data, "32766", "non-empty json marshal")
// invalid values should be encoded as null
null := NewInt16(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalInt16Text(t *testing.T) {
i := Int16From(32766)
data, err := i.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "32766", "non-empty text marshal")
// invalid values should be encoded as null
null := NewInt16(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestInt16Pointer(t *testing.T) {
i := Int16From(32766)
ptr := i.Ptr()
if *ptr != 32766 {
t.Errorf("bad %s int16: %#v ≠ %d\n", "pointer", ptr, 32766)
}
null := NewInt16(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s int16: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestInt16IsZero(t *testing.T) {
i := Int16From(32766)
if i.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewInt16(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewInt16(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestInt16SetValid(t *testing.T) {
change := NewInt16(0, false)
assertNullInt16(t, change, "SetValid()")
change.SetValid(32766)
assertInt16(t, change, "SetValid()")
}
func TestInt16Scan(t *testing.T) {
var i Int16
err := i.Scan(32766)
maybePanic(err)
assertInt16(t, i, "scanned int16")
var null Int16
err = null.Scan(nil)
maybePanic(err)
assertNullInt16(t, null, "scanned null")
}
func assertInt16(t *testing.T, i Int16, from string) {
if i.Int16 != 32766 {
t.Errorf("bad %s int16: %d ≠ %d\n", from, i.Int16, 32766)
}
if !i.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullInt16(t *testing.T, i Int16, from string) {
if i.Valid {
t.Error(from, "is valid, but should be invalid")
}
}

145
int32.go Normal file
View file

@ -0,0 +1,145 @@
package null
import (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// NullInt32 is a replica of sql.NullInt64 for int32 types.
type NullInt32 struct {
Int32 int32
Valid bool
}
// Int32 is an nullable int32.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Int32 struct {
NullInt32
}
// NewInt32 creates a new Int32
func NewInt32(i int32, valid bool) Int32 {
return Int32{
NullInt32: NullInt32{
Int32: i,
Valid: valid,
},
}
}
// Int32From creates a new Int32 that will always be valid.
func Int32From(i int32) Int32 {
return NewInt32(i, true)
}
// Int32FromPtr creates a new Int32 that be null if i is nil.
func Int32FromPtr(i *int32) Int32 {
if i == nil {
return NewInt32(0, false)
}
return NewInt32(*i, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Int32.
// It also supports unmarshalling a sql.NullInt32.
func (i *Int32) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch v.(type) {
case float64:
// Unmarshal again, directly to int32, to avoid intermediate float64
err = json.Unmarshal(data, &i.Int32)
case map[string]interface{}:
err = json.Unmarshal(data, &i.NullInt32)
case nil:
i.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Int32", reflect.TypeOf(v).Name())
}
i.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Int32 if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (i *Int32) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
i.Valid = false
return nil
}
var err error
res, err := strconv.ParseInt(string(text), 10, 32)
i.Valid = err == nil
if i.Valid {
i.Int32 = int32(res)
}
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Int32 is null.
func (i Int32) MarshalJSON() ([]byte, error) {
if !i.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatInt(int64(i.Int32), 10)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Int32 is null.
func (i Int32) MarshalText() ([]byte, error) {
if !i.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatInt(int64(i.Int32), 10)), nil
}
// SetValid changes this Int32's value and also sets it to be non-null.
func (i *Int32) SetValid(n int32) {
i.Int32 = n
i.Valid = true
}
// Ptr returns a pointer to this Int32's value, or a nil pointer if this Int32 is null.
func (i Int32) Ptr() *int32 {
if !i.Valid {
return nil
}
return &i.Int32
}
// IsZero returns true for invalid Int32's, for future omitempty support (Go 1.4?)
// A non-null Int32 with a 0 value will not be considered zero.
func (i Int32) IsZero() bool {
return !i.Valid
}
// Scan implements the Scanner interface.
func (n *NullInt32) Scan(value interface{}) error {
if value == nil {
n.Int32, n.Valid = 0, false
return nil
}
n.Valid = true
return convertAssign(&n.Int32, value)
}
// Value implements the driver Valuer interface.
func (n NullInt32) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Int32, nil
}

196
int32_test.go Normal file
View file

@ -0,0 +1,196 @@
package null
import (
"encoding/json"
"math"
"strconv"
"testing"
)
var (
int32JSON = []byte(`2147483646`)
nullInt32JSON = []byte(`{"Int32":2147483646,"Valid":true}`)
)
func TestInt32From(t *testing.T) {
i := Int32From(2147483646)
assertInt32(t, i, "Int32From()")
zero := Int32From(0)
if !zero.Valid {
t.Error("Int32From(0)", "is invalid, but should be valid")
}
}
func TestInt32FromPtr(t *testing.T) {
n := int32(2147483646)
iptr := &n
i := Int32FromPtr(iptr)
assertInt32(t, i, "Int32FromPtr()")
null := Int32FromPtr(nil)
assertNullInt32(t, null, "Int32FromPtr(nil)")
}
func TestUnmarshalInt32(t *testing.T) {
var i Int32
err := json.Unmarshal(int32JSON, &i)
maybePanic(err)
assertInt32(t, i, "int32 json")
var ni Int32
err = json.Unmarshal(nullInt32JSON, &ni)
maybePanic(err)
assertInt32(t, ni, "sq.NullInt32 json")
var null Int32
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullInt32(t, null, "null json")
var badType Int32
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullInt32(t, badType, "wrong type json")
var invalid Int32
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
assertNullInt32(t, invalid, "invalid json")
}
func TestUnmarshalNonIntegerNumber32(t *testing.T) {
var i Int32
err := json.Unmarshal(float64JSON, &i)
if err == nil {
panic("err should be present; non-integer number coerced to int32")
}
}
func TestUnmarshalInt32Overflow(t *testing.T) {
int32Overflow := uint32(math.MaxInt32)
// Max int32 should decode successfully
var i Int32
err := json.Unmarshal([]byte(strconv.FormatUint(uint64(int32Overflow), 10)), &i)
maybePanic(err)
// Attempt to overflow
int32Overflow++
err = json.Unmarshal([]byte(strconv.FormatUint(uint64(int32Overflow), 10)), &i)
if err == nil {
panic("err should be present; decoded value overflows int32")
}
}
func TestTextUnmarshalInt32(t *testing.T) {
var i Int32
err := i.UnmarshalText([]byte("2147483646"))
maybePanic(err)
assertInt32(t, i, "UnmarshalText() int32")
var blank Int32
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullInt32(t, blank, "UnmarshalText() empty int32")
var null Int32
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullInt32(t, null, `UnmarshalText() "null"`)
}
func TestMarshalInt32(t *testing.T) {
i := Int32From(2147483646)
data, err := json.Marshal(i)
maybePanic(err)
assertJSONEquals(t, data, "2147483646", "non-empty json marshal")
// invalid values should be encoded as null
null := NewInt32(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalInt32Text(t *testing.T) {
i := Int32From(2147483646)
data, err := i.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "2147483646", "non-empty text marshal")
// invalid values should be encoded as null
null := NewInt32(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestInt32Pointer(t *testing.T) {
i := Int32From(2147483646)
ptr := i.Ptr()
if *ptr != 2147483646 {
t.Errorf("bad %s int32: %#v ≠ %d\n", "pointer", ptr, 2147483646)
}
null := NewInt32(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s int32: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestInt32IsZero(t *testing.T) {
i := Int32From(2147483646)
if i.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewInt32(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewInt32(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestInt32SetValid(t *testing.T) {
change := NewInt32(0, false)
assertNullInt32(t, change, "SetValid()")
change.SetValid(2147483646)
assertInt32(t, change, "SetValid()")
}
func TestInt32Scan(t *testing.T) {
var i Int32
err := i.Scan(2147483646)
maybePanic(err)
assertInt32(t, i, "scanned int32")
var null Int32
err = null.Scan(nil)
maybePanic(err)
assertNullInt32(t, null, "scanned null")
}
func assertInt32(t *testing.T, i Int32, from string) {
if i.Int32 != 2147483646 {
t.Errorf("bad %s int32: %d ≠ %d\n", from, i.Int32, 2147483646)
}
if !i.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullInt32(t *testing.T, i Int32, from string) {
if i.Valid {
t.Error(from, "is valid, but should be invalid")
}
}

118
int64.go Normal file
View file

@ -0,0 +1,118 @@
package null
import (
"database/sql"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// Int64 is an nullable int64.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Int64 struct {
sql.NullInt64
}
// NewInt64 creates a new Int64
func NewInt64(i int64, valid bool) Int64 {
return Int64{
NullInt64: sql.NullInt64{
Int64: i,
Valid: valid,
},
}
}
// Int64From creates a new Int64 that will always be valid.
func Int64From(i int64) Int64 {
return NewInt64(i, true)
}
// Int64FromPtr creates a new Int64 that be null if i is nil.
func Int64FromPtr(i *int64) Int64 {
if i == nil {
return NewInt64(0, false)
}
return NewInt64(*i, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Int64.
// It also supports unmarshalling a sql.NullInt64.
func (i *Int64) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch v.(type) {
case float64:
// Unmarshal again, directly to int64, to avoid intermediate float64
err = json.Unmarshal(data, &i.Int64)
case map[string]interface{}:
err = json.Unmarshal(data, &i.NullInt64)
case nil:
i.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Int64", reflect.TypeOf(v).Name())
}
i.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Int64 if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (i *Int64) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
i.Valid = false
return nil
}
var err error
i.Int64, err = strconv.ParseInt(string(text), 10, 64)
i.Valid = err == nil
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Int64 is null.
func (i Int64) MarshalJSON() ([]byte, error) {
if !i.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatInt(i.Int64, 10)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Int64 is null.
func (i Int64) MarshalText() ([]byte, error) {
if !i.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatInt(i.Int64, 10)), nil
}
// SetValid changes this Int64's value and also sets it to be non-null.
func (i *Int64) SetValid(n int64) {
i.Int64 = n
i.Valid = true
}
// Ptr returns a pointer to this Int64's value, or a nil pointer if this Int64 is null.
func (i Int64) Ptr() *int64 {
if !i.Valid {
return nil
}
return &i.Int64
}
// IsZero returns true for invalid Int64's, for future omitempty support (Go 1.4?)
// A non-null Int64 with a 0 value will not be considered zero.
func (i Int64) IsZero() bool {
return !i.Valid
}

196
int64_test.go Normal file
View file

@ -0,0 +1,196 @@
package null
import (
"encoding/json"
"math"
"strconv"
"testing"
)
var (
int64JSON = []byte(`9223372036854775806`)
nullInt64JSON = []byte(`{"Int64":9223372036854775806,"Valid":true}`)
)
func TestInt64From(t *testing.T) {
i := Int64From(9223372036854775806)
assertInt64(t, i, "Int64From()")
zero := Int64From(0)
if !zero.Valid {
t.Error("Int64From(0)", "is invalid, but should be valid")
}
}
func TestInt64FromPtr(t *testing.T) {
n := int64(9223372036854775806)
iptr := &n
i := Int64FromPtr(iptr)
assertInt64(t, i, "Int64FromPtr()")
null := Int64FromPtr(nil)
assertNullInt64(t, null, "Int64FromPtr(nil)")
}
func TestUnmarshalInt64(t *testing.T) {
var i Int64
err := json.Unmarshal(int64JSON, &i)
maybePanic(err)
assertInt64(t, i, "int64 json")
var ni Int64
err = json.Unmarshal(nullInt64JSON, &ni)
maybePanic(err)
assertInt64(t, ni, "sq.NullInt64 json")
var null Int64
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullInt64(t, null, "null json")
var badType Int64
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullInt64(t, badType, "wrong type json")
var invalid Int64
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
assertNullInt64(t, invalid, "invalid json")
}
func TestUnmarshalNonIntegerNumber64(t *testing.T) {
var i Int64
err := json.Unmarshal(float64JSON, &i)
if err == nil {
panic("err should be present; non-integer number coerced to int64")
}
}
func TestUnmarshalInt64Overflow(t *testing.T) {
int64Overflow := uint64(math.MaxInt64)
// Max int64 should decode successfully
var i Int64
err := json.Unmarshal([]byte(strconv.FormatUint(uint64(int64Overflow), 10)), &i)
maybePanic(err)
// Attempt to overflow
int64Overflow++
err = json.Unmarshal([]byte(strconv.FormatUint(uint64(int64Overflow), 10)), &i)
if err == nil {
panic("err should be present; decoded value overflows int64")
}
}
func TestTextUnmarshalInt64(t *testing.T) {
var i Int64
err := i.UnmarshalText([]byte("9223372036854775806"))
maybePanic(err)
assertInt64(t, i, "UnmarshalText() int64")
var blank Int64
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullInt64(t, blank, "UnmarshalText() empty int64")
var null Int64
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullInt64(t, null, `UnmarshalText() "null"`)
}
func TestMarshalInt64(t *testing.T) {
i := Int64From(9223372036854775806)
data, err := json.Marshal(i)
maybePanic(err)
assertJSONEquals(t, data, "9223372036854775806", "non-empty json marshal")
// invalid values should be encoded as null
null := NewInt64(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalInt64Text(t *testing.T) {
i := Int64From(9223372036854775806)
data, err := i.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "9223372036854775806", "non-empty text marshal")
// invalid values should be encoded as null
null := NewInt64(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestInt64Pointer(t *testing.T) {
i := Int64From(9223372036854775806)
ptr := i.Ptr()
if *ptr != 9223372036854775806 {
t.Errorf("bad %s int64: %#v ≠ %d\n", "pointer", ptr, 9223372036854775806)
}
null := NewInt64(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s int64: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestInt64IsZero(t *testing.T) {
i := Int64From(9223372036854775806)
if i.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewInt64(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewInt64(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestInt64SetValid(t *testing.T) {
change := NewInt64(0, false)
assertNullInt64(t, change, "SetValid()")
change.SetValid(9223372036854775806)
assertInt64(t, change, "SetValid()")
}
func TestInt64Scan(t *testing.T) {
var i Int64
err := i.Scan(9223372036854775806)
maybePanic(err)
assertInt64(t, i, "scanned int64")
var null Int64
err = null.Scan(nil)
maybePanic(err)
assertNullInt64(t, null, "scanned null")
}
func assertInt64(t *testing.T, i Int64, from string) {
if i.Int64 != 9223372036854775806 {
t.Errorf("bad %s int64: %d ≠ %d\n", from, i.Int64, 9223372036854775806)
}
if !i.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullInt64(t *testing.T, i Int64, from string) {
if i.Valid {
t.Error(from, "is valid, but should be invalid")
}
}

145
int8.go Normal file
View file

@ -0,0 +1,145 @@
package null
import (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// NullInt8 is a replica of sql.NullInt64 for int8 types.
type NullInt8 struct {
Int8 int8
Valid bool
}
// Int8 is an nullable int8.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Int8 struct {
NullInt8
}
// NewInt8 creates a new Int8
func NewInt8(i int8, valid bool) Int8 {
return Int8{
NullInt8: NullInt8{
Int8: i,
Valid: valid,
},
}
}
// Int8From creates a new Int8 that will always be valid.
func Int8From(i int8) Int8 {
return NewInt8(i, true)
}
// Int8FromPtr creates a new Int8 that be null if i is nil.
func Int8FromPtr(i *int8) Int8 {
if i == nil {
return NewInt8(0, false)
}
return NewInt8(*i, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Int8.
// It also supports unmarshalling a sql.NullInt8.
func (i *Int8) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch v.(type) {
case float64:
// Unmarshal again, directly to int8, to avoid intermediate float64
err = json.Unmarshal(data, &i.Int8)
case map[string]interface{}:
err = json.Unmarshal(data, &i.NullInt8)
case nil:
i.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Int8", reflect.TypeOf(v).Name())
}
i.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Int8 if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (i *Int8) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
i.Valid = false
return nil
}
var err error
res, err := strconv.ParseInt(string(text), 10, 8)
i.Valid = err == nil
if i.Valid {
i.Int8 = int8(res)
}
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Int8 is null.
func (i Int8) MarshalJSON() ([]byte, error) {
if !i.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatInt(int64(i.Int8), 10)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Int8 is null.
func (i Int8) MarshalText() ([]byte, error) {
if !i.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatInt(int64(i.Int8), 10)), nil
}
// SetValid changes this Int8's value and also sets it to be non-null.
func (i *Int8) SetValid(n int8) {
i.Int8 = n
i.Valid = true
}
// Ptr returns a pointer to this Int8's value, or a nil pointer if this Int8 is null.
func (i Int8) Ptr() *int8 {
if !i.Valid {
return nil
}
return &i.Int8
}
// IsZero returns true for invalid Int8's, for future omitempty support (Go 1.4?)
// A non-null Int8 with a 0 value will not be considered zero.
func (i Int8) IsZero() bool {
return !i.Valid
}
// Scan implements the Scanner interface.
func (n *NullInt8) Scan(value interface{}) error {
if value == nil {
n.Int8, n.Valid = 0, false
return nil
}
n.Valid = true
return convertAssign(&n.Int8, value)
}
// Value implements the driver Valuer interface.
func (n NullInt8) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Int8, nil
}

196
int8_test.go Normal file
View file

@ -0,0 +1,196 @@
package null
import (
"encoding/json"
"math"
"strconv"
"testing"
)
var (
int8JSON = []byte(`126`)
nullInt8JSON = []byte(`{"Int8":126,"Valid":true}`)
)
func TestInt8From(t *testing.T) {
i := Int8From(126)
assertInt8(t, i, "Int8From()")
zero := Int8From(0)
if !zero.Valid {
t.Error("Int8From(0)", "is invalid, but should be valid")
}
}
func TestInt8FromPtr(t *testing.T) {
n := int8(126)
iptr := &n
i := Int8FromPtr(iptr)
assertInt8(t, i, "Int8FromPtr()")
null := Int8FromPtr(nil)
assertNullInt8(t, null, "Int8FromPtr(nil)")
}
func TestUnmarshalInt8(t *testing.T) {
var i Int8
err := json.Unmarshal(int8JSON, &i)
maybePanic(err)
assertInt8(t, i, "int8 json")
var ni Int8
err = json.Unmarshal(nullInt8JSON, &ni)
maybePanic(err)
assertInt8(t, ni, "sq.NullInt8 json")
var null Int8
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullInt8(t, null, "null json")
var badType Int8
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullInt8(t, badType, "wrong type json")
var invalid Int8
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
assertNullInt8(t, invalid, "invalid json")
}
func TestUnmarshalNonIntegerNumber8(t *testing.T) {
var i Int8
err := json.Unmarshal(float64JSON, &i)
if err == nil {
panic("err should be present; non-integer number coerced to int8")
}
}
func TestUnmarshalInt8Overflow(t *testing.T) {
int8Overflow := uint8(math.MaxInt8)
// Max int8 should decode successfully
var i Int8
err := json.Unmarshal([]byte(strconv.FormatUint(uint64(int8Overflow), 10)), &i)
maybePanic(err)
// Attempt to overflow
int8Overflow++
err = json.Unmarshal([]byte(strconv.FormatUint(uint64(int8Overflow), 10)), &i)
if err == nil {
panic("err should be present; decoded value overflows int8")
}
}
func TestTextUnmarshalInt8(t *testing.T) {
var i Int8
err := i.UnmarshalText([]byte("126"))
maybePanic(err)
assertInt8(t, i, "UnmarshalText() int8")
var blank Int8
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullInt8(t, blank, "UnmarshalText() empty int8")
var null Int8
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullInt8(t, null, `UnmarshalText() "null"`)
}
func TestMarshalInt8(t *testing.T) {
i := Int8From(126)
data, err := json.Marshal(i)
maybePanic(err)
assertJSONEquals(t, data, "126", "non-empty json marshal")
// invalid values should be encoded as null
null := NewInt8(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalInt8Text(t *testing.T) {
i := Int8From(126)
data, err := i.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "126", "non-empty text marshal")
// invalid values should be encoded as null
null := NewInt8(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestInt8Pointer(t *testing.T) {
i := Int8From(126)
ptr := i.Ptr()
if *ptr != 126 {
t.Errorf("bad %s int8: %#v ≠ %d\n", "pointer", ptr, 126)
}
null := NewInt8(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s int8: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestInt8IsZero(t *testing.T) {
i := Int8From(126)
if i.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewInt8(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewInt8(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestInt8SetValid(t *testing.T) {
change := NewInt8(0, false)
assertNullInt8(t, change, "SetValid()")
change.SetValid(126)
assertInt8(t, change, "SetValid()")
}
func TestInt8Scan(t *testing.T) {
var i Int8
err := i.Scan(126)
maybePanic(err)
assertInt8(t, i, "scanned int8")
var null Int8
err = null.Scan(nil)
maybePanic(err)
assertNullInt8(t, null, "scanned null")
}
func assertInt8(t *testing.T, i Int8, from string) {
if i.Int8 != 126 {
t.Errorf("bad %s int8: %d ≠ %d\n", from, i.Int8, 126)
}
if !i.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullInt8(t *testing.T, i Int8, from string) {
if i.Valid {
t.Error(from, "is valid, but should be invalid")
}
}

View file

@ -2,14 +2,12 @@ package null
import (
"encoding/json"
"math"
"strconv"
"testing"
)
var (
intJSON = []byte(`12345`)
nullIntJSON = []byte(`{"Int64":12345,"Valid":true}`)
nullIntJSON = []byte(`{"Int":12345,"Valid":true}`)
)
func TestIntFrom(t *testing.T) {
@ -23,7 +21,7 @@ func TestIntFrom(t *testing.T) {
}
func TestIntFromPtr(t *testing.T) {
n := int64(12345)
n := int(12345)
iptr := &n
i := IntFromPtr(iptr)
assertInt(t, i, "IntFromPtr()")
@ -41,7 +39,7 @@ func TestUnmarshalInt(t *testing.T) {
var ni Int
err = json.Unmarshal(nullIntJSON, &ni)
maybePanic(err)
assertInt(t, ni, "sq.NullInt64 json")
assertInt(t, ni, "sq.NullInt json")
var null Int
err = json.Unmarshal(nullJSON, &null)
@ -65,28 +63,12 @@ func TestUnmarshalInt(t *testing.T) {
func TestUnmarshalNonIntegerNumber(t *testing.T) {
var i Int
err := json.Unmarshal(floatJSON, &i)
err := json.Unmarshal(float64JSON, &i)
if err == nil {
panic("err should be present; non-integer number coerced to int")
}
}
func TestUnmarshalInt64Overflow(t *testing.T) {
int64Overflow := uint64(math.MaxInt64)
// Max int64 should decode successfully
var i Int
err := json.Unmarshal([]byte(strconv.FormatUint(int64Overflow, 10)), &i)
maybePanic(err)
// Attempt to overflow
int64Overflow++
err = json.Unmarshal([]byte(strconv.FormatUint(int64Overflow, 10)), &i)
if err == nil {
panic("err should be present; decoded value overflows int64")
}
}
func TestTextUnmarshalInt(t *testing.T) {
var i Int
err := i.UnmarshalText([]byte("12345"))
@ -181,8 +163,8 @@ func TestIntScan(t *testing.T) {
}
func assertInt(t *testing.T, i Int, from string) {
if i.Int64 != 12345 {
t.Errorf("bad %s int: %d ≠ %d\n", from, i.Int64, 12345)
if i.Int != 12345 {
t.Errorf("bad %s int: %d ≠ %d\n", from, i.Int, 12345)
}
if !i.Valid {
t.Error(from, "is invalid, but should be valid")

145
uint.go Normal file
View file

@ -0,0 +1,145 @@
package null
import (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// NullUint is a replica of sql.NullInt64 for uint types.
type NullUint struct {
Uint uint
Valid bool
}
// Uint is an nullable uint.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Uint struct {
NullUint
}
// NewUint creates a new Uint
func NewUint(i uint, valid bool) Uint {
return Uint{
NullUint: NullUint{
Uint: i,
Valid: valid,
},
}
}
// UintFrom creates a new Uint that will always be valid.
func UintFrom(i uint) Uint {
return NewUint(i, true)
}
// UintFromPtr creates a new Uint that be null if i is nil.
func UintFromPtr(i *uint) Uint {
if i == nil {
return NewUint(0, false)
}
return NewUint(*i, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Uint.
// It also supports unmarshalling a sql.NullUint.
func (i *Uint) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch v.(type) {
case float64:
// Unmarshal again, directly to uint, to avoid intermediate float64
err = json.Unmarshal(data, &i.Uint)
case map[string]interface{}:
err = json.Unmarshal(data, &i.NullUint)
case nil:
i.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Uint", reflect.TypeOf(v).Name())
}
i.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Uint if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (i *Uint) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
i.Valid = false
return nil
}
var err error
res, err := strconv.ParseUint(string(text), 10, 0)
i.Valid = err == nil
if i.Valid {
i.Uint = uint(res)
}
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Uint is null.
func (i Uint) MarshalJSON() ([]byte, error) {
if !i.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatUint(uint64(i.Uint), 10)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Uint is null.
func (i Uint) MarshalText() ([]byte, error) {
if !i.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatUint(uint64(i.Uint), 10)), nil
}
// SetValid changes this Uint's value and also sets it to be non-null.
func (i *Uint) SetValid(n uint) {
i.Uint = n
i.Valid = true
}
// Ptr returns a pointer to this Uint's value, or a nil pointer if this Uint is null.
func (i Uint) Ptr() *uint {
if !i.Valid {
return nil
}
return &i.Uint
}
// IsZero returns true for invalid Uints, for future omitempty support (Go 1.4?)
// A non-null Uint with a 0 value will not be considered zero.
func (i Uint) IsZero() bool {
return !i.Valid
}
// Scan implements the Scanner interface.
func (n *NullUint) Scan(value interface{}) error {
if value == nil {
n.Uint, n.Valid = 0, false
return nil
}
n.Valid = true
return convertAssign(&n.Uint, value)
}
// Value implements the driver Valuer interface.
func (n NullUint) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Uint, nil
}

145
uint16.go Normal file
View file

@ -0,0 +1,145 @@
package null
import (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// NullUint16 is a replica of sql.NullInt64 for uint16 types.
type NullUint16 struct {
Uint16 uint16
Valid bool
}
// Uint16 is an nullable uint16.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Uint16 struct {
NullUint16
}
// NewUint16 creates a new Uint16
func NewUint16(i uint16, valid bool) Uint16 {
return Uint16{
NullUint16: NullUint16{
Uint16: i,
Valid: valid,
},
}
}
// Uint16From creates a new Uint16 that will always be valid.
func Uint16From(i uint16) Uint16 {
return NewUint16(i, true)
}
// Uint16FromPtr creates a new Uint16 that be null if i is nil.
func Uint16FromPtr(i *uint16) Uint16 {
if i == nil {
return NewUint16(0, false)
}
return NewUint16(*i, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Uint16.
// It also supports unmarshalling a sql.NullUint16.
func (i *Uint16) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch v.(type) {
case float64:
// Unmarshal again, directly to uint16, to avoid intermediate float64
err = json.Unmarshal(data, &i.Uint16)
case map[string]interface{}:
err = json.Unmarshal(data, &i.NullUint16)
case nil:
i.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Uint16", reflect.TypeOf(v).Name())
}
i.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Uint16 if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (i *Uint16) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
i.Valid = false
return nil
}
var err error
res, err := strconv.ParseUint(string(text), 10, 16)
i.Valid = err == nil
if i.Valid {
i.Uint16 = uint16(res)
}
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Uint16 is null.
func (i Uint16) MarshalJSON() ([]byte, error) {
if !i.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatUint(uint64(i.Uint16), 10)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Uint16 is null.
func (i Uint16) MarshalText() ([]byte, error) {
if !i.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatUint(uint64(i.Uint16), 10)), nil
}
// SetValid changes this Uint16's value and also sets it to be non-null.
func (i *Uint16) SetValid(n uint16) {
i.Uint16 = n
i.Valid = true
}
// Ptr returns a pointer to this Uint16's value, or a nil pointer if this Uint16 is null.
func (i Uint16) Ptr() *uint16 {
if !i.Valid {
return nil
}
return &i.Uint16
}
// IsZero returns true for invalid Uint16's, for future omitempty support (Go 1.4?)
// A non-null Uint16 with a 0 value will not be considered zero.
func (i Uint16) IsZero() bool {
return !i.Valid
}
// Scan implements the Scanner interface.
func (n *NullUint16) Scan(value interface{}) error {
if value == nil {
n.Uint16, n.Valid = 0, false
return nil
}
n.Valid = true
return convertAssign(&n.Uint16, value)
}
// Value implements the driver Valuer interface.
func (n NullUint16) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Uint16, nil
}

196
uint16_test.go Normal file
View file

@ -0,0 +1,196 @@
package null
import (
"encoding/json"
"math"
"strconv"
"testing"
)
var (
uint16JSON = []byte(`65534`)
nullUint16JSON = []byte(`{"Uint16":65534,"Valid":true}`)
)
func TestUint16From(t *testing.T) {
i := Uint16From(65534)
assertUint16(t, i, "Uint16From()")
zero := Uint16From(0)
if !zero.Valid {
t.Error("Uint16From(0)", "is invalid, but should be valid")
}
}
func TestUint16FromPtr(t *testing.T) {
n := uint16(65534)
iptr := &n
i := Uint16FromPtr(iptr)
assertUint16(t, i, "Uint16FromPtr()")
null := Uint16FromPtr(nil)
assertNullUint16(t, null, "Uint16FromPtr(nil)")
}
func TestUnmarshalUint16(t *testing.T) {
var i Uint16
err := json.Unmarshal(uint16JSON, &i)
maybePanic(err)
assertUint16(t, i, "uint16 json")
var ni Uint16
err = json.Unmarshal(nullUint16JSON, &ni)
maybePanic(err)
assertUint16(t, ni, "sq.NullUint16 json")
var null Uint16
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullUint16(t, null, "null json")
var badType Uint16
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullUint16(t, badType, "wrong type json")
var invalid Uint16
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
assertNullUint16(t, invalid, "invalid json")
}
func TestUnmarshalNonUintegerNumber16(t *testing.T) {
var i Uint16
err := json.Unmarshal(float64JSON, &i)
if err == nil {
panic("err should be present; non-integer number coerced to uint16")
}
}
func TestUnmarshalUint16Overflow(t *testing.T) {
uint16Overflow := int64(math.MaxUint16)
// Max uint16 should decode successfully
var i Uint16
err := json.Unmarshal([]byte(strconv.FormatUint(uint64(uint16Overflow), 10)), &i)
maybePanic(err)
// Attempt to overflow
uint16Overflow++
err = json.Unmarshal([]byte(strconv.FormatUint(uint64(uint16Overflow), 10)), &i)
if err == nil {
panic("err should be present; decoded value overflows uint16")
}
}
func TestTextUnmarshalUint16(t *testing.T) {
var i Uint16
err := i.UnmarshalText([]byte("65534"))
maybePanic(err)
assertUint16(t, i, "UnmarshalText() uint16")
var blank Uint16
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullUint16(t, blank, "UnmarshalText() empty uint16")
var null Uint16
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullUint16(t, null, `UnmarshalText() "null"`)
}
func TestMarshalUint16(t *testing.T) {
i := Uint16From(65534)
data, err := json.Marshal(i)
maybePanic(err)
assertJSONEquals(t, data, "65534", "non-empty json marshal")
// invalid values should be encoded as null
null := NewUint16(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalUint16Text(t *testing.T) {
i := Uint16From(65534)
data, err := i.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "65534", "non-empty text marshal")
// invalid values should be encoded as null
null := NewUint16(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestUint16Pointer(t *testing.T) {
i := Uint16From(65534)
ptr := i.Ptr()
if *ptr != 65534 {
t.Errorf("bad %s uint16: %#v ≠ %d\n", "pointer", ptr, 65534)
}
null := NewUint16(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s uint16: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestUint16IsZero(t *testing.T) {
i := Uint16From(65534)
if i.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewUint16(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewUint16(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestUint16SetValid(t *testing.T) {
change := NewUint16(0, false)
assertNullUint16(t, change, "SetValid()")
change.SetValid(65534)
assertUint16(t, change, "SetValid()")
}
func TestUint16Scan(t *testing.T) {
var i Uint16
err := i.Scan(65534)
maybePanic(err)
assertUint16(t, i, "scanned uint16")
var null Uint16
err = null.Scan(nil)
maybePanic(err)
assertNullUint16(t, null, "scanned null")
}
func assertUint16(t *testing.T, i Uint16, from string) {
if i.Uint16 != 65534 {
t.Errorf("bad %s uint16: %d ≠ %d\n", from, i.Uint16, 65534)
}
if !i.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullUint16(t *testing.T, i Uint16, from string) {
if i.Valid {
t.Error(from, "is valid, but should be invalid")
}
}

145
uint32.go Normal file
View file

@ -0,0 +1,145 @@
package null
import (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// NullUint32 is a replica of sql.NullInt64 for uint32 types.
type NullUint32 struct {
Uint32 uint32
Valid bool
}
// Uint32 is an nullable uint32.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Uint32 struct {
NullUint32
}
// NewUint32 creates a new Uint32
func NewUint32(i uint32, valid bool) Uint32 {
return Uint32{
NullUint32: NullUint32{
Uint32: i,
Valid: valid,
},
}
}
// Uint32From creates a new Uint32 that will always be valid.
func Uint32From(i uint32) Uint32 {
return NewUint32(i, true)
}
// Uint32FromPtr creates a new Uint32 that be null if i is nil.
func Uint32FromPtr(i *uint32) Uint32 {
if i == nil {
return NewUint32(0, false)
}
return NewUint32(*i, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Uint32.
// It also supports unmarshalling a sql.NullUint32.
func (i *Uint32) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch v.(type) {
case float64:
// Unmarshal again, directly to uint32, to avoid intermediate float64
err = json.Unmarshal(data, &i.Uint32)
case map[string]interface{}:
err = json.Unmarshal(data, &i.NullUint32)
case nil:
i.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Uint32", reflect.TypeOf(v).Name())
}
i.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Uint32 if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (i *Uint32) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
i.Valid = false
return nil
}
var err error
res, err := strconv.ParseUint(string(text), 10, 32)
i.Valid = err == nil
if i.Valid {
i.Uint32 = uint32(res)
}
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Uint32 is null.
func (i Uint32) MarshalJSON() ([]byte, error) {
if !i.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatUint(uint64(i.Uint32), 10)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Uint32 is null.
func (i Uint32) MarshalText() ([]byte, error) {
if !i.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatUint(uint64(i.Uint32), 10)), nil
}
// SetValid changes this Uint32's value and also sets it to be non-null.
func (i *Uint32) SetValid(n uint32) {
i.Uint32 = n
i.Valid = true
}
// Ptr returns a pointer to this Uint32's value, or a nil pointer if this Uint32 is null.
func (i Uint32) Ptr() *uint32 {
if !i.Valid {
return nil
}
return &i.Uint32
}
// IsZero returns true for invalid Uint32's, for future omitempty support (Go 1.4?)
// A non-null Uint32 with a 0 value will not be considered zero.
func (i Uint32) IsZero() bool {
return !i.Valid
}
// Scan implements the Scanner interface.
func (n *NullUint32) Scan(value interface{}) error {
if value == nil {
n.Uint32, n.Valid = 0, false
return nil
}
n.Valid = true
return convertAssign(&n.Uint32, value)
}
// Value implements the driver Valuer interface.
func (n NullUint32) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Uint32, nil
}

196
uint32_test.go Normal file
View file

@ -0,0 +1,196 @@
package null
import (
"encoding/json"
"math"
"strconv"
"testing"
)
var (
uint32JSON = []byte(`4294967294`)
nullUint32JSON = []byte(`{"Uint32":4294967294,"Valid":true}`)
)
func TestUint32From(t *testing.T) {
i := Uint32From(4294967294)
assertUint32(t, i, "Uint32From()")
zero := Uint32From(0)
if !zero.Valid {
t.Error("Uint32From(0)", "is invalid, but should be valid")
}
}
func TestUint32FromPtr(t *testing.T) {
n := uint32(4294967294)
iptr := &n
i := Uint32FromPtr(iptr)
assertUint32(t, i, "Uint32FromPtr()")
null := Uint32FromPtr(nil)
assertNullUint32(t, null, "Uint32FromPtr(nil)")
}
func TestUnmarshalUint32(t *testing.T) {
var i Uint32
err := json.Unmarshal(uint32JSON, &i)
maybePanic(err)
assertUint32(t, i, "uint32 json")
var ni Uint32
err = json.Unmarshal(nullUint32JSON, &ni)
maybePanic(err)
assertUint32(t, ni, "sq.NullUint32 json")
var null Uint32
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullUint32(t, null, "null json")
var badType Uint32
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullUint32(t, badType, "wrong type json")
var invalid Uint32
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
assertNullUint32(t, invalid, "invalid json")
}
func TestUnmarshalNonUintegerNumber32(t *testing.T) {
var i Uint32
err := json.Unmarshal(float64JSON, &i)
if err == nil {
panic("err should be present; non-integer number coerced to uint32")
}
}
func TestUnmarshalUint32Overflow(t *testing.T) {
uint32Overflow := int64(math.MaxUint32)
// Max uint32 should decode successfully
var i Uint32
err := json.Unmarshal([]byte(strconv.FormatUint(uint64(uint32Overflow), 10)), &i)
maybePanic(err)
// Attempt to overflow
uint32Overflow++
err = json.Unmarshal([]byte(strconv.FormatUint(uint64(uint32Overflow), 10)), &i)
if err == nil {
panic("err should be present; decoded value overflows uint32")
}
}
func TestTextUnmarshalUint32(t *testing.T) {
var i Uint32
err := i.UnmarshalText([]byte("4294967294"))
maybePanic(err)
assertUint32(t, i, "UnmarshalText() uint32")
var blank Uint32
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullUint32(t, blank, "UnmarshalText() empty uint32")
var null Uint32
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullUint32(t, null, `UnmarshalText() "null"`)
}
func TestMarshalUint32(t *testing.T) {
i := Uint32From(4294967294)
data, err := json.Marshal(i)
maybePanic(err)
assertJSONEquals(t, data, "4294967294", "non-empty json marshal")
// invalid values should be encoded as null
null := NewUint32(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalUint32Text(t *testing.T) {
i := Uint32From(4294967294)
data, err := i.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "4294967294", "non-empty text marshal")
// invalid values should be encoded as null
null := NewUint32(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestUint32Pointer(t *testing.T) {
i := Uint32From(4294967294)
ptr := i.Ptr()
if *ptr != 4294967294 {
t.Errorf("bad %s uint32: %#v ≠ %d\n", "pointer", ptr, 4294967294)
}
null := NewUint32(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s uint32: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestUint32IsZero(t *testing.T) {
i := Uint32From(4294967294)
if i.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewUint32(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewUint32(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestUint32SetValid(t *testing.T) {
change := NewUint32(0, false)
assertNullUint32(t, change, "SetValid()")
change.SetValid(4294967294)
assertUint32(t, change, "SetValid()")
}
func TestUint32Scan(t *testing.T) {
var i Uint32
err := i.Scan(4294967294)
maybePanic(err)
assertUint32(t, i, "scanned uint32")
var null Uint32
err = null.Scan(nil)
maybePanic(err)
assertNullUint32(t, null, "scanned null")
}
func assertUint32(t *testing.T, i Uint32, from string) {
if i.Uint32 != 4294967294 {
t.Errorf("bad %s uint32: %d ≠ %d\n", from, i.Uint32, 4294967294)
}
if !i.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullUint32(t *testing.T, i Uint32, from string) {
if i.Valid {
t.Error(from, "is valid, but should be invalid")
}
}

145
uint64.go Normal file
View file

@ -0,0 +1,145 @@
package null
import (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// NullUint64 is a replica of sql.NullInt64 for uint64 types.
type NullUint64 struct {
Uint64 uint64
Valid bool
}
// Uint64 is an nullable uint64.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Uint64 struct {
NullUint64
}
// NewUint64 creates a new Uint64
func NewUint64(i uint64, valid bool) Uint64 {
return Uint64{
NullUint64: NullUint64{
Uint64: i,
Valid: valid,
},
}
}
// Uint64From creates a new Uint64 that will always be valid.
func Uint64From(i uint64) Uint64 {
return NewUint64(i, true)
}
// Uint64FromPtr creates a new Uint64 that be null if i is nil.
func Uint64FromPtr(i *uint64) Uint64 {
if i == nil {
return NewUint64(0, false)
}
return NewUint64(*i, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Uint64.
// It also supports unmarshalling a sql.NullUint64.
func (i *Uint64) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch v.(type) {
case float64:
// Unmarshal again, directly to uint64, to avoid intermediate float64
err = json.Unmarshal(data, &i.Uint64)
case map[string]interface{}:
err = json.Unmarshal(data, &i.NullUint64)
case nil:
i.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Uint64", reflect.TypeOf(v).Name())
}
i.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Uint64 if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (i *Uint64) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
i.Valid = false
return nil
}
var err error
res, err := strconv.ParseUint(string(text), 10, 64)
i.Valid = err == nil
if i.Valid {
i.Uint64 = uint64(res)
}
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Uint64 is null.
func (i Uint64) MarshalJSON() ([]byte, error) {
if !i.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatUint(i.Uint64, 10)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Uint64 is null.
func (i Uint64) MarshalText() ([]byte, error) {
if !i.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatUint(i.Uint64, 10)), nil
}
// SetValid changes this Uint64's value and also sets it to be non-null.
func (i *Uint64) SetValid(n uint64) {
i.Uint64 = n
i.Valid = true
}
// Ptr returns a pointer to this Uint64's value, or a nil pointer if this Uint64 is null.
func (i Uint64) Ptr() *uint64 {
if !i.Valid {
return nil
}
return &i.Uint64
}
// IsZero returns true for invalid Uint64's, for future omitempty support (Go 1.4?)
// A non-null Uint64 with a 0 value will not be considered zero.
func (i Uint64) IsZero() bool {
return !i.Valid
}
// Scan implements the Scanner interface.
func (n *NullUint64) Scan(value interface{}) error {
if value == nil {
n.Uint64, n.Valid = 0, false
return nil
}
n.Valid = true
return convertAssign(&n.Uint64, value)
}
// Value implements the driver Valuer interface.
func (n NullUint64) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Uint64, nil
}

178
uint64_test.go Normal file
View file

@ -0,0 +1,178 @@
package null
import (
"encoding/json"
"testing"
)
var (
uint64JSON = []byte(`18446744073709551614`)
nullUint64JSON = []byte(`{"Uint64":18446744073709551614,"Valid":true}`)
)
func TestUint64From(t *testing.T) {
i := Uint64From(18446744073709551614)
assertUint64(t, i, "Uint64From()")
zero := Uint64From(0)
if !zero.Valid {
t.Error("Uint64From(0)", "is invalid, but should be valid")
}
}
func TestUint64FromPtr(t *testing.T) {
n := uint64(18446744073709551614)
iptr := &n
i := Uint64FromPtr(iptr)
assertUint64(t, i, "Uint64FromPtr()")
null := Uint64FromPtr(nil)
assertNullUint64(t, null, "Uint64FromPtr(nil)")
}
func TestUnmarshalUint64(t *testing.T) {
var i Uint64
err := json.Unmarshal(uint64JSON, &i)
maybePanic(err)
assertUint64(t, i, "uint64 json")
var ni Uint64
err = json.Unmarshal(nullUint64JSON, &ni)
maybePanic(err)
assertUint64(t, ni, "sq.NullUint64 json")
var null Uint64
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullUint64(t, null, "null json")
var badType Uint64
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullUint64(t, badType, "wrong type json")
var invalid Uint64
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
assertNullUint64(t, invalid, "invalid json")
}
func TestUnmarshalNonUintegerNumber64(t *testing.T) {
var i Uint64
err := json.Unmarshal(float64JSON, &i)
if err == nil {
panic("err should be present; non-integer number coerced to uint64")
}
}
func TestTextUnmarshalUint64(t *testing.T) {
var i Uint64
err := i.UnmarshalText([]byte("18446744073709551614"))
maybePanic(err)
assertUint64(t, i, "UnmarshalText() uint64")
var blank Uint64
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullUint64(t, blank, "UnmarshalText() empty uint64")
var null Uint64
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullUint64(t, null, `UnmarshalText() "null"`)
}
func TestMarshalUint64(t *testing.T) {
i := Uint64From(18446744073709551614)
data, err := json.Marshal(i)
maybePanic(err)
assertJSONEquals(t, data, "18446744073709551614", "non-empty json marshal")
// invalid values should be encoded as null
null := NewUint64(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalUint64Text(t *testing.T) {
i := Uint64From(18446744073709551614)
data, err := i.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "18446744073709551614", "non-empty text marshal")
// invalid values should be encoded as null
null := NewUint64(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestUint64Pointer(t *testing.T) {
i := Uint64From(18446744073709551614)
ptr := i.Ptr()
if *ptr != 18446744073709551614 {
t.Errorf("bad %s uint64: %#v ≠ %d\n", "pointer", ptr, uint64(18446744073709551614))
}
null := NewUint64(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s uint64: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestUint64IsZero(t *testing.T) {
i := Uint64From(18446744073709551614)
if i.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewUint64(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewUint64(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestUint64SetValid(t *testing.T) {
change := NewUint64(0, false)
assertNullUint64(t, change, "SetValid()")
change.SetValid(18446744073709551614)
assertUint64(t, change, "SetValid()")
}
func TestUint64Scan(t *testing.T) {
var i Uint64
err := i.Scan(uint64(18446744073709551614))
maybePanic(err)
assertUint64(t, i, "scanned uint64")
var null Uint64
err = null.Scan(nil)
maybePanic(err)
assertNullUint64(t, null, "scanned null")
}
func assertUint64(t *testing.T, i Uint64, from string) {
if i.Uint64 != 18446744073709551614 {
t.Errorf("bad %s uint64: %d ≠ %d\n", from, i.Uint64, uint64(18446744073709551614))
}
if !i.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullUint64(t *testing.T, i Uint64, from string) {
if i.Valid {
t.Error(from, "is valid, but should be invalid")
}
}

145
uint8.go Normal file
View file

@ -0,0 +1,145 @@
package null
import (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// NullUint8 is a replica of sql.NullInt64 for uint8 types.
type NullUint8 struct {
Uint8 uint8
Valid bool
}
// Uint8 is an nullable uint8.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Uint8 struct {
NullUint8
}
// NewUint8 creates a new Uint8
func NewUint8(i uint8, valid bool) Uint8 {
return Uint8{
NullUint8: NullUint8{
Uint8: i,
Valid: valid,
},
}
}
// Uint8From creates a new Uint8 that will always be valid.
func Uint8From(i uint8) Uint8 {
return NewUint8(i, true)
}
// Uint8FromPtr creates a new Uint8 that be null if i is nil.
func Uint8FromPtr(i *uint8) Uint8 {
if i == nil {
return NewUint8(0, false)
}
return NewUint8(*i, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Uint8.
// It also supports unmarshalling a sql.NullUint8.
func (i *Uint8) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch v.(type) {
case float64:
// Unmarshal again, directly to uint8, to avoid intermediate float64
err = json.Unmarshal(data, &i.Uint8)
case map[string]interface{}:
err = json.Unmarshal(data, &i.NullUint8)
case nil:
i.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Uint8", reflect.TypeOf(v).Name())
}
i.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Uint8 if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (i *Uint8) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
i.Valid = false
return nil
}
var err error
res, err := strconv.ParseUint(string(text), 10, 8)
i.Valid = err == nil
if i.Valid {
i.Uint8 = uint8(res)
}
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Uint8 is null.
func (i Uint8) MarshalJSON() ([]byte, error) {
if !i.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatUint(uint64(i.Uint8), 10)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Uint8 is null.
func (i Uint8) MarshalText() ([]byte, error) {
if !i.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatUint(uint64(i.Uint8), 10)), nil
}
// SetValid changes this Uint8's value and also sets it to be non-null.
func (i *Uint8) SetValid(n uint8) {
i.Uint8 = n
i.Valid = true
}
// Ptr returns a pointer to this Uint8's value, or a nil pointer if this Uint8 is null.
func (i Uint8) Ptr() *uint8 {
if !i.Valid {
return nil
}
return &i.Uint8
}
// IsZero returns true for invalid Uint8's, for future omitempty support (Go 1.4?)
// A non-null Uint8 with a 0 value will not be considered zero.
func (i Uint8) IsZero() bool {
return !i.Valid
}
// Scan implements the Scanner interface.
func (n *NullUint8) Scan(value interface{}) error {
if value == nil {
n.Uint8, n.Valid = 0, false
return nil
}
n.Valid = true
return convertAssign(&n.Uint8, value)
}
// Value implements the driver Valuer interface.
func (n NullUint8) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Uint8, nil
}

196
uint8_test.go Normal file
View file

@ -0,0 +1,196 @@
package null
import (
"encoding/json"
"math"
"strconv"
"testing"
)
var (
uint8JSON = []byte(`254`)
nullUint8JSON = []byte(`{"Uint8":254,"Valid":true}`)
)
func TestUint8From(t *testing.T) {
i := Uint8From(254)
assertUint8(t, i, "Uint8From()")
zero := Uint8From(0)
if !zero.Valid {
t.Error("Uint8From(0)", "is invalid, but should be valid")
}
}
func TestUint8FromPtr(t *testing.T) {
n := uint8(254)
iptr := &n
i := Uint8FromPtr(iptr)
assertUint8(t, i, "Uint8FromPtr()")
null := Uint8FromPtr(nil)
assertNullUint8(t, null, "Uint8FromPtr(nil)")
}
func TestUnmarshalUint8(t *testing.T) {
var i Uint8
err := json.Unmarshal(uint8JSON, &i)
maybePanic(err)
assertUint8(t, i, "uint8 json")
var ni Uint8
err = json.Unmarshal(nullUint8JSON, &ni)
maybePanic(err)
assertUint8(t, ni, "sq.NullUint8 json")
var null Uint8
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullUint8(t, null, "null json")
var badType Uint8
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullUint8(t, badType, "wrong type json")
var invalid Uint8
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
assertNullUint8(t, invalid, "invalid json")
}
func TestUnmarshalNonUintegerNumber8(t *testing.T) {
var i Uint8
err := json.Unmarshal(float64JSON, &i)
if err == nil {
panic("err should be present; non-integer number coerced to uint8")
}
}
func TestUnmarshalUint8Overflow(t *testing.T) {
uint8Overflow := int64(math.MaxUint8)
// Max uint8 should decode successfully
var i Uint8
err := json.Unmarshal([]byte(strconv.FormatUint(uint64(uint8Overflow), 10)), &i)
maybePanic(err)
// Attempt to overflow
uint8Overflow++
err = json.Unmarshal([]byte(strconv.FormatUint(uint64(uint8Overflow), 10)), &i)
if err == nil {
panic("err should be present; decoded value overflows uint8")
}
}
func TestTextUnmarshalUint8(t *testing.T) {
var i Uint8
err := i.UnmarshalText([]byte("254"))
maybePanic(err)
assertUint8(t, i, "UnmarshalText() uint8")
var blank Uint8
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullUint8(t, blank, "UnmarshalText() empty uint8")
var null Uint8
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullUint8(t, null, `UnmarshalText() "null"`)
}
func TestMarshalUint8(t *testing.T) {
i := Uint8From(254)
data, err := json.Marshal(i)
maybePanic(err)
assertJSONEquals(t, data, "254", "non-empty json marshal")
// invalid values should be encoded as null
null := NewUint8(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalUint8Text(t *testing.T) {
i := Uint8From(254)
data, err := i.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "254", "non-empty text marshal")
// invalid values should be encoded as null
null := NewUint8(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestUint8Pointer(t *testing.T) {
i := Uint8From(254)
ptr := i.Ptr()
if *ptr != 254 {
t.Errorf("bad %s uint8: %#v ≠ %d\n", "pointer", ptr, 254)
}
null := NewUint8(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s uint8: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestUint8IsZero(t *testing.T) {
i := Uint8From(254)
if i.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewUint8(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewUint8(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestUint8SetValid(t *testing.T) {
change := NewUint8(0, false)
assertNullUint8(t, change, "SetValid()")
change.SetValid(254)
assertUint8(t, change, "SetValid()")
}
func TestUint8Scan(t *testing.T) {
var i Uint8
err := i.Scan(254)
maybePanic(err)
assertUint8(t, i, "scanned uint8")
var null Uint8
err = null.Scan(nil)
maybePanic(err)
assertNullUint8(t, null, "scanned null")
}
func assertUint8(t *testing.T, i Uint8, from string) {
if i.Uint8 != 254 {
t.Errorf("bad %s uint8: %d ≠ %d\n", from, i.Uint8, 254)
}
if !i.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullUint8(t *testing.T, i Uint8, from string) {
if i.Valid {
t.Error(from, "is valid, but should be invalid")
}
}

178
uint_test.go Normal file
View file

@ -0,0 +1,178 @@
package null
import (
"encoding/json"
"testing"
)
var (
uintJSON = []byte(`12345`)
nullUintJSON = []byte(`{"Uint":12345,"Valid":true}`)
)
func TestUintFrom(t *testing.T) {
i := UintFrom(12345)
assertUint(t, i, "UintFrom()")
zero := UintFrom(0)
if !zero.Valid {
t.Error("UintFrom(0)", "is invalid, but should be valid")
}
}
func TestUintFromPtr(t *testing.T) {
n := uint(12345)
iptr := &n
i := UintFromPtr(iptr)
assertUint(t, i, "UintFromPtr()")
null := UintFromPtr(nil)
assertNullUint(t, null, "UintFromPtr(nil)")
}
func TestUnmarshalUint(t *testing.T) {
var i Uint
err := json.Unmarshal(uintJSON, &i)
maybePanic(err)
assertUint(t, i, "uint json")
var ni Uint
err = json.Unmarshal(nullUintJSON, &ni)
maybePanic(err)
assertUint(t, ni, "sq.NullUint json")
var null Uint
err = json.Unmarshal(nullJSON, &null)
maybePanic(err)
assertNullUint(t, null, "null json")
var badType Uint
err = json.Unmarshal(boolJSON, &badType)
if err == nil {
panic("err should not be nil")
}
assertNullUint(t, badType, "wrong type json")
var invalid Uint
err = invalid.UnmarshalJSON(invalidJSON)
if _, ok := err.(*json.SyntaxError); !ok {
t.Errorf("expected json.SyntaxError, not %T", err)
}
assertNullUint(t, invalid, "invalid json")
}
func TestUnmarshalNonUintegerNumber(t *testing.T) {
var i Uint
err := json.Unmarshal(float64JSON, &i)
if err == nil {
panic("err should be present; non-uinteger number coerced to uint")
}
}
func TestTextUnmarshalUint(t *testing.T) {
var i Uint
err := i.UnmarshalText([]byte("12345"))
maybePanic(err)
assertUint(t, i, "UnmarshalText() uint")
var blank Uint
err = blank.UnmarshalText([]byte(""))
maybePanic(err)
assertNullUint(t, blank, "UnmarshalText() empty uint")
var null Uint
err = null.UnmarshalText([]byte("null"))
maybePanic(err)
assertNullUint(t, null, `UnmarshalText() "null"`)
}
func TestMarshalUint(t *testing.T) {
i := UintFrom(12345)
data, err := json.Marshal(i)
maybePanic(err)
assertJSONEquals(t, data, "12345", "non-empty json marshal")
// invalid values should be encoded as null
null := NewUint(0, false)
data, err = json.Marshal(null)
maybePanic(err)
assertJSONEquals(t, data, "null", "null json marshal")
}
func TestMarshalUintText(t *testing.T) {
i := UintFrom(12345)
data, err := i.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "12345", "non-empty text marshal")
// invalid values should be encoded as null
null := NewUint(0, false)
data, err = null.MarshalText()
maybePanic(err)
assertJSONEquals(t, data, "", "null text marshal")
}
func TestUintPointer(t *testing.T) {
i := UintFrom(12345)
ptr := i.Ptr()
if *ptr != 12345 {
t.Errorf("bad %s uint: %#v ≠ %d\n", "pointer", ptr, 12345)
}
null := NewUint(0, false)
ptr = null.Ptr()
if ptr != nil {
t.Errorf("bad %s uint: %#v ≠ %s\n", "nil pointer", ptr, "nil")
}
}
func TestUintIsZero(t *testing.T) {
i := UintFrom(12345)
if i.IsZero() {
t.Errorf("IsZero() should be false")
}
null := NewUint(0, false)
if !null.IsZero() {
t.Errorf("IsZero() should be true")
}
zero := NewUint(0, true)
if zero.IsZero() {
t.Errorf("IsZero() should be false")
}
}
func TestUintSetValid(t *testing.T) {
change := NewUint(0, false)
assertNullUint(t, change, "SetValid()")
change.SetValid(12345)
assertUint(t, change, "SetValid()")
}
func TestUintScan(t *testing.T) {
var i Uint
err := i.Scan(12345)
maybePanic(err)
assertUint(t, i, "scanned uint")
var null Uint
err = null.Scan(nil)
maybePanic(err)
assertNullUint(t, null, "scanned null")
}
func assertUint(t *testing.T, i Uint, from string) {
if i.Uint != 12345 {
t.Errorf("bad %s uint: %d ≠ %d\n", from, i.Uint, 12345)
}
if !i.Valid {
t.Error(from, "is invalid, but should be valid")
}
}
func assertNullUint(t *testing.T, i Uint, from string) {
if i.Valid {
t.Error(from, "is valid, but should be invalid")
}
}