package datatypes
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"reflect"
"strconv"
"time"
)
type NullString = Null [string ]
type NullInt64 = Null [int64 ]
type NullInt32 = Null [int32 ]
type NullInt16 = Null [int16 ]
type NullByte = Null [byte ]
type NullFloat64 = Null [float64 ]
type NullBool = Null [bool ]
type NullTime = Null [time .Time ]
type Null [T any ] struct {
V T
Valid bool
}
func (n *Null [T ]) Scan (value any ) error {
if value == nil {
n .V , n .Valid = *new (T ), false
return nil
}
n .Valid = true
return convertAssign (&n .V , value )
}
func (n Null [T ]) Value () (driver .Value , error ) {
if !n .Valid {
return nil , nil
}
return n .V , nil
}
func NewNull [T any ](v T ) Null [T ] {
return Null [T ]{V : v , Valid : true }
}
var errNilPtr = errors .New ("destination pointer is nil" )
func convertAssign(dest , src any ) error {
return convertAssignRows (dest , src , nil )
}
func convertAssignRows(dest , src any , rows *sql .Rows ) error {
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 *sql .RawBytes :
if d == nil {
return errNilPtr
}
*d = append ((*d )[:0 ], s ...)
return nil
}
case []byte :
switch d := dest .(type ) {
case *string :
if d == nil {
return errNilPtr
}
*d = string (s )
return nil
case *any :
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 *time .Time :
*d = s
return nil
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 *sql .RawBytes :
if d == nil {
return errNilPtr
}
*d = s .AppendFormat ((*d )[:0 ], time .RFC3339Nano )
return nil
}
case decimalDecompose :
switch d := dest .(type ) {
case decimalCompose :
return d .Compose (s .Decompose (nil ))
}
case nil :
switch d := dest .(type ) {
case *any :
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 *any :
*d = src
return nil
}
if scanner , ok := dest .(sql .Scanner ); ok {
return scanner .Scan (src )
}
dpv := reflect .ValueOf (dest )
if dpv .Kind () != reflect .Pointer {
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 ()) {
switch b := src .(type ) {
case []byte :
dv .Set (reflect .ValueOf (cloneBytes (b )))
default :
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 .Pointer :
if src == nil {
dv .Set (reflect .Zero (dv .Type ()))
return nil
}
dv .Set (reflect .New (dv .Type ().Elem ()))
return convertAssignRows (dv .Interface (), src , rows )
case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
if src == nil {
return fmt .Errorf ("converting NULL to %s is unsupported" , dv .Kind ())
}
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 :
if src == nil {
return fmt .Errorf ("converting NULL to %s is unsupported" , dv .Kind ())
}
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 :
if src == nil {
return fmt .Errorf ("converting NULL to %s is unsupported" , dv .Kind ())
}
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
case reflect .String :
if src == nil {
return fmt .Errorf ("converting NULL to %s is unsupported" , dv .Kind ())
}
switch v := src .(type ) {
case string :
dv .SetString (v )
return nil
case []byte :
dv .SetString (string (v ))
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 asString(src any ) 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
}
type decimalDecompose interface {
Decompose(buf []byte ) (form byte , negative bool , coefficient []byte , exponent int32 )
}
type decimalCompose interface {
Compose(form byte , negative bool , coefficient []byte , exponent int32 ) error
}
func cloneBytes(b []byte ) []byte {
if b == nil {
return nil
}
return append ([]byte {}, b ...)
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .