Skip to content

Commit

Permalink
small maint
Browse files Browse the repository at this point in the history
  • Loading branch information
blockloop committed Mar 1, 2018
1 parent a317c0c commit 78c774a
Show file tree
Hide file tree
Showing 15 changed files with 258 additions and 247 deletions.
5 changes: 2 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.1"
revision = "a72618"

[prune]
go-tests = true
Expand Down
14 changes: 12 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
MOCKGEN=$(GOPATH)/bin/mockgen
GOFILES=$(shell find . -type f -iname '*.go')
IFACEFILES=$(shell grep -Pirl 'type .* interface' --exclude-dir vendor)

mocks/mocks.go: $(MOCKGEN)
$(MOCKGEN) -source=scanner.go -destination=mocks/mocks.go -package=mocks
build: $(GOFILES)
go build -o /dev/null *.go
.PHONY: build

test:
go test -tags=integration ./...
.PHONY: test

internal/mocks/mocks.go: interface.go | $(MOCKGEN)
$(MOCKGEN) -source=$< -destination=$@ -package=mocks

$(MOCKGEN):
go get github.com/golang/mock/mockgen
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,21 @@ vals := scan.Values([]string{"ID", "Name"}, user)
I find that the usefulness of both Values and Columns lies within using a library such as [sq][].

```go
sq.Insert(userCols).
vals := scan.Values(userCols, &user)
sq.Insert(userCols...).
Into("users").
Values(scan.Values(userCols, &user))
Values(vals...)
```


## Why

While many other awesome db project support similar features (i.e. [sqlx](https://github.com/jmoiron/sqlx)) this provides the ability to use other projects like [sq][] to write fluent sql statements and pass the resulting `row` to `scan` for simple scanning
While many other awesome db project support similar features (i.e. [sqlx](https://github.com/jmoiron/sqlx)) this provides the ability to use the stdlib or [squirrel][sq] to write fluent sql statements and pass the resulting `row` to `scan` for scanning


## Benchmarks

I created some benchmarks in [bench_scanner_test.go](bench_scanner_test.go) to compare using `scan` against manually scanning directly to structs and/or appending to slices. The results aren't staggering as you can see. Roughly 850ns for one field structs and 4.6μs for five field structs.
I created some benchmarks in [bench_scanner_test.go](bench_scanner_test.go) to compare using `scan` against manually scanning directly to structs and/or appending to slices. The results aren't staggering as you can see.

```
> go test -benchtime=10s -bench=.
Expand Down
70 changes: 23 additions & 47 deletions bench_scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,26 @@ import (
"testing"

"github.com/blockloop/scan"
"github.com/stretchr/testify/require"
. "github.com/stretchr/testify/require"

_ "github.com/mattn/go-sqlite3"
)

func BenchmarkScanRowOneField(b *testing.B) {
func mustDB(t testing.TB, schema string) *sql.DB {
db, err := sql.Open("sqlite3", ":memory:")
require.NoError(b, err)
NoError(t, err)

_, err = db.Exec(`CREATE TABLE persons (
_, err = db.Exec(schema)
NoError(t, err)
return db
}

func BenchmarkSqliteScanRowOneField(b *testing.B) {
db := mustDB(b, `CREATE TABLE persons (
name VARCHAR(120),
age TINYINT
);
INSERT INTO PERSONS (name) VALUES ('brett')
`)
require.NoError(b, err)
INSERT INTO PERSONS (name) VALUES ('brett')`)

type item struct {
First string
Expand All @@ -35,18 +38,13 @@ func BenchmarkScanRowOneField(b *testing.B) {
}
}

func BenchmarkDirectScanOneField(b *testing.B) {
db, err := sql.Open("sqlite3", ":memory:")
require.NoError(b, err)

_, err = db.Exec(`CREATE TABLE persons (
func BenchmarkSqliteDirectScanOneField(b *testing.B) {
db := mustDB(b, `CREATE TABLE persons (
name VARCHAR(120),
age TINYINT
);
INSERT INTO PERSONS (name) VALUES ('brett')
`)
require.NoError(b, err)

type item struct {
First string
Expand All @@ -61,22 +59,17 @@ func BenchmarkDirectScanOneField(b *testing.B) {
}
}

func BenchmarkScanRowFiveFields(b *testing.B) {
db, err := sql.Open("sqlite3", ":memory:")
require.NoError(b, err)

_, err = db.Exec(`CREATE TABLE persons (
func BenchmarkSqliteScanRowFiveFields(b *testing.B) {
db := mustDB(b, `CREATE TABLE persons (
name VARCHAR(120),
age TINYINT,
active BOOLEAN,
city VARCHAR(60),
state VARCHAR(12)
);
INSERT INTO PERSONS (name, age, active, city, state)
VALUES ('brett', 100, 1, 'dallas', 'tx');
`)
require.NoError(b, err)

type item struct {
First string `db:"first"`
Expand All @@ -94,22 +87,17 @@ func BenchmarkScanRowFiveFields(b *testing.B) {
}
}

func BenchmarkDirectScanFiveFields(b *testing.B) {
db, err := sql.Open("sqlite3", ":memory:")
require.NoError(b, err)

_, err = db.Exec(`CREATE TABLE persons (
func BenchmarkSqliteDirectScanFiveFields(b *testing.B) {
db := mustDB(b, `CREATE TABLE persons (
name VARCHAR(120),
age TINYINT,
active BOOLEAN,
city VARCHAR(60),
state VARCHAR(12)
);
INSERT INTO PERSONS (name, age, active, city, state)
VALUES ('brett', 100, 1, 'dallas', 'tx');
`)
require.NoError(b, err)

type item struct {
First string `db:"first"`
Expand All @@ -134,15 +122,9 @@ func BenchmarkDirectScanFiveFields(b *testing.B) {
}
}

func BenchmarkScanRowsOneField(b *testing.B) {
db, err := sql.Open("sqlite3", ":memory:")
require.NoError(b, err)

_, err = db.Exec(`CREATE TABLE persons ( name VARCHAR(120) );
INSERT INTO PERSONS (name) VALUES ('brett'), ('fred'), ('george'), ('steve')
`)
require.NoError(b, err)
func BenchmarkSqliteScanRowsOneField(b *testing.B) {
db := mustDB(b, `CREATE TABLE persons ( name VARCHAR(120) );
INSERT INTO PERSONS (name) VALUES ('brett'), ('fred'), ('george'), ('steve')`)

type item struct {
First string `db:"name"`
Expand All @@ -156,15 +138,9 @@ func BenchmarkScanRowsOneField(b *testing.B) {
}
}

func BenchmarkDirectScanManyOneField(b *testing.B) {
db, err := sql.Open("sqlite3", ":memory:")
require.NoError(b, err)

_, err = db.Exec(`CREATE TABLE persons ( name VARCHAR(120) );
INSERT INTO PERSONS (name) VALUES ('brett'), ('fred'), ('george'), ('steve')
`)
require.NoError(b, err)
func BenchmarkSqliteDirectScanManyOneField(b *testing.B) {
db := mustDB(b, `CREATE TABLE persons ( name VARCHAR(120) );
INSERT INTO PERSONS (name) VALUES ('brett'), ('fred'), ('george'), ('steve')`)

type item struct {
First string
Expand Down
4 changes: 0 additions & 4 deletions columns.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,3 @@ func supportedColumnType(k reflect.Kind) bool {
return false
}
}

type scanner interface {
Scan(...interface{}) error
}
66 changes: 42 additions & 24 deletions columns_test.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package scan
package scan_test

import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"
"github.com/blockloop/scan"
. "github.com/stretchr/testify/assert"
)

func TestColumnsPanicsWhenNotAPointer(t *testing.T) {
assert.Panics(t, func() {
Columns(1)
Panics(t, func() {
scan.Columns(1)
})
}

func TestColumnsPanicsWhenNotAStruct(t *testing.T) {
var num int
assert.Panics(t, func() {
Columns(&num)
Panics(t, func() {
scan.Columns(&num)
})
}

Expand All @@ -24,17 +26,17 @@ func TestColumnsReturnsFieldNames(t *testing.T) {
Name string
}

cols := Columns(&person{})
assert.EqualValues(t, []string{"Name"}, cols)
cols := scan.Columns(&person{})
EqualValues(t, []string{"Name"}, cols)
}

func TestColumnsReturnsStructTags(t *testing.T) {
type person struct {
Name string `db:"name"`
}

cols := Columns(&person{})
assert.EqualValues(t, []string{"name"}, cols)
cols := scan.Columns(&person{})
EqualValues(t, []string{"name"}, cols)
}

func TestColumnsReturnsStructTagsAndFieldNames(t *testing.T) {
Expand All @@ -43,8 +45,8 @@ func TestColumnsReturnsStructTagsAndFieldNames(t *testing.T) {
Age int
}

cols := Columns(&person{})
assert.EqualValues(t, []string{"name", "Age"}, cols)
cols := scan.Columns(&person{})
EqualValues(t, []string{"name", "Age"}, cols)
}

func TestColumnsIgnoresPrivateFields(t *testing.T) {
Expand All @@ -53,8 +55,8 @@ func TestColumnsIgnoresPrivateFields(t *testing.T) {
Age int
}

cols := Columns(&person{})
assert.EqualValues(t, []string{"Age"}, cols)
cols := scan.Columns(&person{})
EqualValues(t, []string{"Age"}, cols)
}

func TestColumnsAddsComplexTypesWhenStructTag(t *testing.T) {
Expand All @@ -64,8 +66,8 @@ func TestColumnsAddsComplexTypesWhenStructTag(t *testing.T) {
} `db:"address"`
}

cols := Columns(&person{})
assert.EqualValues(t, []string{"address"}, cols)
cols := scan.Columns(&person{})
EqualValues(t, []string{"address"}, cols)
}

func TestColumnsIgnoresComplexTypesWhenNoStructTag(t *testing.T) {
Expand All @@ -75,8 +77,8 @@ func TestColumnsIgnoresComplexTypesWhenNoStructTag(t *testing.T) {
}
}

cols := Columns(&person{})
assert.EqualValues(t, []string{}, cols)
cols := scan.Columns(&person{})
EqualValues(t, []string{}, cols)
}

func TestColumnsExcludesFields(t *testing.T) {
Expand All @@ -85,8 +87,8 @@ func TestColumnsExcludesFields(t *testing.T) {
Age int `db:"age"`
}

cols := ColumnsStrict(&person{}, "name")
assert.EqualValues(t, []string{"age"}, cols)
cols := scan.ColumnsStrict(&person{}, "name")
EqualValues(t, []string{"age"}, cols)
}

func TestColumnsStrictExcludesUntaggedFields(t *testing.T) {
Expand All @@ -95,8 +97,8 @@ func TestColumnsStrictExcludesUntaggedFields(t *testing.T) {
Age int
}

cols := ColumnsStrict(&person{})
assert.EqualValues(t, []string{"name"}, cols)
cols := scan.ColumnsStrict(&person{})
EqualValues(t, []string{"name"}, cols)
}

func TestColumnsIgnoresDashTag(t *testing.T) {
Expand All @@ -105,6 +107,22 @@ func TestColumnsIgnoresDashTag(t *testing.T) {
Age int `db:"-"`
}

cols := ColumnsStrict(&person{})
assert.EqualValues(t, []string{"name"}, cols)
cols := scan.ColumnsStrict(&person{})
EqualValues(t, []string{"name"}, cols)
}

func TestColumnsReturnsAllFieldNames(t *testing.T) {
s := new(largeStruct)
exp := reflect.Indirect(reflect.ValueOf(s)).NumField()

cols := scan.Columns(s)
EqualValues(t, exp, len(cols))
}

func BenchmarkColumnsLargeStruct(b *testing.B) {
ls := &largeStruct{ID: "test", Index: 88, UUID: "test", IsActive: false, Balance: "test", Picture: "test", Age: 88, EyeColor: "test", Name: "test", Gender: "test", Company: "test", Email: "test", Phone: "test", Address: "test", About: "test", Registered: "test", Latitude: 0.566439688205719, Longitude: 0.48440760374069214, Greeting: "test", FavoriteFruit: "test", AID: "test", AIndex: 19, AUUID: "test", AIsActive: true, ABalance: "test", APicture: "test", AAge: 12, AEyeColor: "test", AName: "test", AGender: "test", ACompany: "test", AEmail: "test", APhone: "test", AAddress: "test", AAbout: "test", ARegistered: "test", ALatitude: 0.16338545083999634, ALongitude: 0.24648870527744293, AGreeting: "test", AFavoriteFruit: "test"}

for i := 0; i < b.N; i++ {
scan.Columns(ls)
}
}
Loading

0 comments on commit 78c774a

Please sign in to comment.