Skip to content

Commit

Permalink
feat #11: extend support for database ops via SQL driver
Browse files Browse the repository at this point in the history
  • Loading branch information
ashwingopalsamy authored Dec 26, 2024
2 parents 3aa69c5 + ae63ac1 commit a206a17
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/ash3in/uuidv8

go 1.23.4

require github.com/DATA-DOG/go-sqlmock v1.5.2
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
34 changes: 34 additions & 0 deletions uuidv8.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ package uuidv8

import (
"crypto/rand"
"database/sql/driver"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"time"
)
Expand Down Expand Up @@ -258,3 +260,35 @@ func (u *UUIDv8) UnmarshalJSON(data []byte) error {
*u = *parsed
return nil
}

// Value implements the [driver.Valuer] interface for database writes.
func (u *UUIDv8) Value() (driver.Value, error) {
if u == nil {
return nil, nil // Return nil for a nil UUID
}
if len(u.Node) != 6 {
return nil, fmt.Errorf("invalid UUIDv8: node length must be 6 bytes, got %d bytes", len(u.Node))
}
return ToString(u), nil // Convert to string for database storage
}

// Scan implements the [sql.Scanner] interface for database reads.
func (u *UUIDv8) Scan(value interface{}) error {
switch v := value.(type) {
case string:
parsed, err := FromString(v)
if err != nil {
return err
}
*u = *parsed
return nil
case []byte:
parsed, err := FromString(string(v))
if err != nil {
return err
}
*u = *parsed
return nil
}
return errors.New("unsupported type for UUIDv8")
}
159 changes: 159 additions & 0 deletions uuidv8_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"testing"
"time"

"github.com/DATA-DOG/go-sqlmock"

"github.com/ash3in/uuidv8"
)

Expand Down Expand Up @@ -561,3 +563,160 @@ func TestNewWithParams_MaxValues(t *testing.T) {
t.Errorf("Generated UUID with max values is invalid: %s", uuid)
}
}

func TestUUIDv8_Value(t *testing.T) {
tests := []struct {
name string
uuid *uuidv8.UUIDv8
mockSetup func(mock sqlmock.Sqlmock, uuidStr string)
expectError bool
}{
{
name: "Valid UUIDv8",
uuid: &uuidv8.UUIDv8{
Timestamp: 123456789,
ClockSeq: 0x0800,
Node: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06},
},
mockSetup: func(mock sqlmock.Sqlmock, uuidStr string) {
mock.ExpectExec("INSERT INTO items").
WithArgs(uuidStr).
WillReturnResult(sqlmock.NewResult(1, 1))
},
expectError: false,
},
{
name: "Invalid Node Length",
uuid: &uuidv8.UUIDv8{
Timestamp: 123456789,
ClockSeq: 0x0800,
Node: []byte{0x01, 0x02},
},
mockSetup: func(mock sqlmock.Sqlmock, uuidStr string) {},
expectError: true,
},
{
name: "Nil UUIDv8",
uuid: nil,
mockSetup: func(mock sqlmock.Sqlmock, uuidStr string) {
mock.ExpectExec("INSERT INTO items").
WithArgs(nil).
WillReturnResult(sqlmock.NewResult(1, 1))
},
expectError: false,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Mock database
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create mock database: %v", err)
}
defer db.Close()

var uuidStr string
if test.uuid != nil && len(test.uuid.Node) == 6 {
uuidStr = uuidv8.ToString(test.uuid)
}

test.mockSetup(mock, uuidStr)

_, err = db.Exec("INSERT INTO items (uuid) VALUES (?)", test.uuid)

if (err != nil) != test.expectError {
t.Errorf("Unexpected error status: got %v, want error=%v", err, test.expectError)
}

if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("Unmet expectations: %v", err)
}
})
}
}

func TestUUIDv8_Scan(t *testing.T) {
// Mock database
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create mock database: %v", err)
}
defer db.Close()

uuidStr := "9a3d4049-0e2c-8080-0102-030405060000"

rows := sqlmock.NewRows([]string{"uuid"}).AddRow(uuidStr)
mock.ExpectQuery("SELECT uuid FROM items").WillReturnRows(rows)

var uuid uuidv8.UUIDv8
err = db.QueryRow("SELECT uuid FROM items").Scan(&uuid)
if err != nil {
t.Errorf("Failed to scan mock database value: %v", err)
}

if uuidv8.ToString(&uuid) != uuidStr {
t.Errorf("Expected UUIDv8 %s, got %s", uuidStr, uuidv8.ToString(&uuid))
}

if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("Unmet expectations: %v", err)
}
}

func TestUUIDv8_Value_EdgeCases(t *testing.T) {
tests := []struct {
name string
uuid *uuidv8.UUIDv8
expected interface{}
}{
{"Valid UUIDv8", &uuidv8.UUIDv8{
Timestamp: 123456789,
ClockSeq: 0x0800,
Node: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06},
}, "0000075b-cd15-8880-0102-030405060000"},
{"Nil UUIDv8", nil, nil},
{"Invalid Node Length", &uuidv8.UUIDv8{
Timestamp: 123456789,
ClockSeq: 0x0800,
Node: []byte{0x01, 0x02},
}, nil},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
value, err := test.uuid.Value()
if err != nil && test.expected != nil {
t.Errorf("Unexpected error: %v", err)
}
if value != test.expected {
t.Errorf("Expected %v, got %v", test.expected, value)
}
})
}
}

func TestUUIDv8_Scan_EdgeCases(t *testing.T) {
tests := []struct {
name string
input interface{}
expectError bool
}{
{"Valid String UUID", "0000075b-cd15-8880-0102-030405060000", false},
{"Valid Byte UUID", []byte("9a3d4049-0e2c-8080-0102-030405060000"), false},
{"Invalid String Format", "invalid-uuid", true},
{"Invalid Type", 12345, true},
{"Empty String", "", true},
{"Empty Bytes", []byte{}, true},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var uuid uuidv8.UUIDv8
err := uuid.Scan(test.input)
if (err != nil) != test.expectError {
t.Errorf("Unexpected error status: got %v, want error=%v", err, test.expectError)
}
})
}
}

0 comments on commit a206a17

Please sign in to comment.