-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #30 from moznion/sql_interface
Implements database/sql/driver.Valuer and database/sql.Scanner on Option type
- Loading branch information
Showing
6 changed files
with
263 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,12 @@ | ||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= | ||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= | ||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= | ||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= | ||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= | ||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= | ||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= | ||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= | ||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package optional | ||
|
||
import ( | ||
"database/sql/driver" | ||
"errors" | ||
"time" | ||
) | ||
|
||
var ( | ||
ErrSQLScannerIncompatibleDataType = errors.New("incompatible data type for SQL scanner on Option[T]") | ||
ErrSQLDriverValuerIncompatibleDataType = errors.New("incompatible data type for SQL driver Valuer on Option[T]") | ||
) | ||
|
||
// Scan assigns a value from a database driver. | ||
// This method is required from database/sql.Scanner interface. | ||
func (o *Option[T]) Scan(src any) error { | ||
if src == nil { | ||
*o = None[T]() | ||
return nil | ||
} | ||
|
||
switch src.(type) { | ||
case string, []byte, int64, float64, bool, time.Time: | ||
*o = Some[T](src.(T)) | ||
default: | ||
return ErrSQLScannerIncompatibleDataType | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Value returns a driver Value. | ||
// This method is required from database/sql/driver.Valuer interface. | ||
func (o Option[T]) Value() (driver.Value, error) { | ||
if o.IsNone() { | ||
return nil, nil | ||
} | ||
|
||
v := o.Unwrap() | ||
switch (interface{})(v).(type) { | ||
case string, []byte, int64, float64, bool, time.Time: | ||
return v, nil | ||
default: | ||
return nil, ErrSQLDriverValuerIncompatibleDataType | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
package optional | ||
|
||
import ( | ||
"database/sql" | ||
"database/sql/driver" | ||
"os" | ||
"testing" | ||
"time" | ||
|
||
_ "github.com/mattn/go-sqlite3" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestOption_Scan(t *testing.T) { | ||
o := Some[any](nil) | ||
|
||
err := o.Scan("bar") | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, "bar", o.Unwrap()) | ||
|
||
err = o.Scan([]byte("buz")) | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, []byte("buz"), o.Unwrap()) | ||
|
||
err = o.Scan(int64(42)) | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, 42, o.Unwrap()) | ||
|
||
err = o.Scan(float64(123.456)) | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, 123.456, o.Unwrap()) | ||
|
||
err = o.Scan(true) | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, true, o.Unwrap()) | ||
|
||
now := time.Now() | ||
err = o.Scan(now) | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, now, o.Unwrap()) | ||
} | ||
|
||
func TestOption_Scan_None(t *testing.T) { | ||
o := Some[any](nil) | ||
|
||
err := o.Scan(nil) | ||
assert.NoError(t, err) | ||
assert.True(t, o.IsNone()) | ||
} | ||
|
||
func TestOption_Scan_UnsupportedTypes(t *testing.T) { | ||
o := Some[any](nil) | ||
|
||
err := o.Scan(int32(42)) | ||
assert.ErrorIs(t, err, ErrSQLScannerIncompatibleDataType) | ||
} | ||
|
||
func TestOption_Scan_ScannerInterfaceSatisfaction(t *testing.T) { | ||
o := Some[any]("string") | ||
var s sql.Scanner = &o | ||
assert.NotNil(t, s) | ||
} | ||
|
||
func TestOption_Value(t *testing.T) { | ||
{ | ||
o := Some[string]("foo") | ||
v, err := o.Value() | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, "foo", v) | ||
} | ||
|
||
{ | ||
o := Some[[]byte]([]byte("bar")) | ||
v, err := o.Value() | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, []byte("bar"), v) | ||
} | ||
|
||
{ | ||
o := Some[int64](42) | ||
v, err := o.Value() | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, 42, v) | ||
} | ||
|
||
{ | ||
o := Some[float64](123.456) | ||
v, err := o.Value() | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, 123.456, v) | ||
} | ||
|
||
{ | ||
o := Some[bool](true) | ||
v, err := o.Value() | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, true, v) | ||
} | ||
|
||
{ | ||
now := time.Now() | ||
o := Some[time.Time](now) | ||
v, err := o.Value() | ||
assert.NoError(t, err) | ||
assert.EqualValues(t, now, v) | ||
} | ||
} | ||
|
||
func TestOption_Value_None(t *testing.T) { | ||
o := None[string]() | ||
v, err := o.Value() | ||
assert.NoError(t, err) | ||
assert.Nil(t, v) | ||
} | ||
|
||
func TestOption_Value_UnsupportedTypes(t *testing.T) { | ||
o := Some[int32](0) | ||
_, err := o.Value() | ||
assert.ErrorIs(t, err, ErrSQLDriverValuerIncompatibleDataType) | ||
} | ||
|
||
func TestOption_Value_ValuerInterfaceSatisfaction(t *testing.T) { | ||
o := Some[any]("string") | ||
var s driver.Valuer = &o | ||
assert.NotNil(t, s) | ||
} | ||
|
||
func TestOption_SQLScan(t *testing.T) { | ||
tmpfile, err := os.CreateTemp(os.TempDir(), "testdb") | ||
assert.NoError(t, err) | ||
|
||
db, err := sql.Open("sqlite3", tmpfile.Name()) | ||
assert.NoError(t, err) | ||
defer func() { | ||
_ = db.Close() | ||
}() | ||
|
||
sqlStmt := "CREATE TABLE test_table (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(32));" | ||
_, err = db.Exec(sqlStmt) | ||
assert.NoError(t, err) | ||
|
||
tx, err := db.Begin() | ||
assert.NoError(t, err) | ||
func() { | ||
stmt, err := tx.Prepare("INSERT INTO test_table(id, name) values(?, ?)") | ||
assert.NoError(t, err) | ||
defer func() { | ||
_ = stmt.Close() | ||
}() | ||
_, err = stmt.Exec(1, "foo") | ||
assert.NoError(t, err) | ||
}() | ||
func() { | ||
stmt, err := tx.Prepare("INSERT INTO test_table(id) values(?)") | ||
assert.NoError(t, err) | ||
defer func() { | ||
_ = stmt.Close() | ||
}() | ||
_, err = stmt.Exec(2) | ||
assert.NoError(t, err) | ||
}() | ||
err = tx.Commit() | ||
assert.NoError(t, err) | ||
|
||
var maybeName Option[string] | ||
|
||
row := db.QueryRow("SELECT name FROM test_table WHERE id = 1") | ||
err = row.Scan(&maybeName) | ||
assert.NoError(t, err) | ||
assert.Equal(t, "foo", maybeName.Unwrap()) | ||
|
||
row = db.QueryRow("SELECT name FROM test_table WHERE id = 2") | ||
err = row.Scan(&maybeName) | ||
assert.NoError(t, err) | ||
assert.True(t, maybeName.IsNone()) | ||
} |