Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement sql.Scanner and driver.Valuer interfaces #5

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions uuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ import (
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"database/sql/driver"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"hash"
"net"
Expand Down Expand Up @@ -144,11 +146,21 @@ func Equal(u1 UUID, u2 UUID) bool {
return bytes.Equal(u1[:], u2[:])
}

// Equal returns true if UUID is equal to passed UUID, otherwise returns false
func (u1 UUID) Equal(u2 UUID) bool {
return Equal(u1, u2)
}

// Version returns algorithm version used to generate UUID.
func (u UUID) Version() uint {
return uint(u[6] >> 4)
}

// Size returns the size of the UUID
func (u UUID) Size() int {
return len(u.Bytes())
}

// Variant returns UUID layout variant.
func (u UUID) Variant() uint {
switch {
Expand Down Expand Up @@ -246,6 +258,39 @@ func (u *UUID) UnmarshalBinary(data []byte) (err error) {
return
}

// Scan implements the sql.Scanner interface.
// Supports []byte and string
func (u *UUID) Scan(src interface{}) error {
var (
u2 UUID
err error
)
switch src.(type) {
case []byte:
b := src.([]byte)
// Try to parse as string if length is not 16
if len(b) == 16 {
u2, err = FromBytes(b)
} else {
u2, err = FromString(string(b))
}
case string:
u2, err = FromString(src.(string))
default:
return errors.New("uuid: unsupported source format")
}
if err != nil {
return fmt.Errorf("uuid: error scanning: %s", err)
}
(*u) = u2
return nil
}

// Value implements the driver.Valuer interface.
func (u UUID) Value() (driver.Value, error) {
return u.String(), nil
}

// FromBytes returns UUID converted from raw byte slice input.
// It will return error if the slice isn't 16 bytes long.
func FromBytes(input []byte) (u UUID, err error) {
Expand Down Expand Up @@ -359,3 +404,29 @@ func newFromHash(h hash.Hash, ns UUID, name string) UUID {

return u
}

// Support gogoprotobuf

func (u *UUID) Unmarshal(data []byte) (err error) {
if len(data) == 0 {
u = nil
return nil
}

*u, err = FromBytes(data)
return nil
}

func (u UUID) MarshalTo(data []byte) (int, error) {
if len(u) == 0 {
return 0, nil
}
return copy(data, u.Bytes()), nil
}

func (u UUID) Marshal() ([]byte, error) {
if len(u) == 0 {
return nil, nil
}
return u.Bytes(), nil
}
125 changes: 125 additions & 0 deletions uuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ func TestEqual(t *testing.T) {
}
}

func TestEqualWithReceiver(t *testing.T) {
if !NamespaceDNS.Equal(NamespaceDNS) {
t.Errorf("Incorrect comparison of %s and %s", NamespaceDNS, NamespaceDNS)
}

if NamespaceDNS.Equal(NamespaceURL) {
t.Errorf("Incorrect comparison of %s and %s", NamespaceDNS, NamespaceURL)
}
}

func TestOr(t *testing.T) {
u1 := UUID{0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff}
u2 := UUID{0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00}
Expand Down Expand Up @@ -82,6 +92,12 @@ func TestVersion(t *testing.T) {
}
}

func TestSize(t *testing.T) {
if NamespaceDNS.Size() != 16 {
t.Errorf("Incorrect size for UUID: %d", NamespaceDNS.Size())
}
}

func TestSetVersion(t *testing.T) {
u := UUID{}
u.SetVersion(4)
Expand Down Expand Up @@ -184,6 +200,66 @@ func TestUnmarshalBinary(t *testing.T) {
}
}

func TestScan(t *testing.T) {
u1, err := FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
if err != nil {
t.Errorf("Error parsing UUID from string: %s", err)
}

u2 := UUID{}
err = u2.Scan(u1.String())
if err != nil {
t.Errorf("Error scanning UUID: %s", err)
}
if !Equal(u1, u2) {
t.Errorf("UUIDs should be equal: %s and %s", u1, u2)
}

u3 := UUID{}
err = u3.Scan(u1.Bytes())
if err != nil {
t.Errorf("Error scanning UUID: %s", err)
}
if !Equal(u1, u3) {
t.Errorf("UUIDs should be equal: %s and %s", u1, u2)
}

u4 := UUID{}
err = u4.Scan([]byte(u1.String()))
if err != nil {
t.Errorf("Error scanning UUID: %s", err)
}
if !Equal(u1, u4) {
t.Errorf("UUIDs should be equal: %s and %s", u1, u2)
}

u5 := UUID{}
err = u5.Scan("invalid-uuid-string")
if err == nil {
t.Errorf("Should return error trying to parse an invalid string, got %s", err)
}

u6 := UUID{}
err = u6.Scan(1)
if err == nil {
t.Errorf("Should return error trying to parse an int, got %s", err)
}
}

func TestValue(t *testing.T) {
u, err := FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
if err != nil {
t.Errorf("Error parsing UUID from string: %s", err)
}
val, err := u.Value()
if err != nil {
t.Errorf("Error getting UUID value: %s", err)
}
if val != u.String() {
t.Errorf("Wrong value returned, should be equal: %s and %s", val, u)
}
}

func TestFromString(t *testing.T) {
u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}

Expand Down Expand Up @@ -397,3 +473,52 @@ func TestNewV5(t *testing.T) {
t.Errorf("UUIDv3 generated same UUIDs for sane names in different namespaces: %s and %s", u1, u4)
}
}

func TestUnmarshal(t *testing.T) {
u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}
u1 := UUID{}
b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}

if err := u1.Unmarshal(b1); err != nil {
t.Errorf("Error unmarshalling UUID from bytes: %s", err)
}

if !Equal(u, u1) {
t.Errorf("UUIDs should be equal: %s and %s", u, u1)
}

b2 := []byte{}

if err := u1.Unmarshal(b2); err != nil {
t.Errorf("Should not return error parsing from empty byte slice, got %s", err)
}
}

func TestMarshalTo(t *testing.T) {
u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}
buf := make([]byte, 16)

if _, err := u.MarshalTo(buf); err != nil {
t.Errorf("Error marshalling UUID to bytes: %s", err)
}

if !bytes.Equal(buf, u.Bytes()) {
t.Errorf("UUID byte slices should be equal: %s and %s", u.Bytes(), buf)
}
}

func TestMarshal(t *testing.T) {
u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}

bytes1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}

bytes2, err := u.Marshal()

if err != nil {
t.Error(err)
}

if !bytes.Equal(bytes2, bytes1) {
t.Errorf("Incorrect bytes representation for UUID: %s", u)
}
}