diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..91bd4f4a --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,140 @@ +version: 2.1 + +"-": &go-versions + [ "1.18.10", "1.19.13", "1.20.14", "1.21.9", "1.22.2" ] + +executors: + go_executor: + parameters: + version: + type: string + docker: + - image: cimg/go:<< parameters.version >> + +jobs: + test: + parameters: + go_version: + type: string + executor: + name: go_executor + version: << parameters.go_version >> + steps: + - checkout + - restore_cache: + keys: + - go-mod-v4-{{ checksum "go.sum" }} + - run: + name: Install Dependencies + command: go mod download + - save_cache: + key: go-mod-v4-{{ checksum "go.sum" }} + paths: + - "/go/pkg/mod" + - run: + name: Run tests + command: | + mkdir -p /tmp/test-reports + gotestsum --junitfile /tmp/test-reports/unit-tests.xml + - store_test_results: + path: /tmp/test-reports + test-race: + parameters: + go_version: + type: string + executor: + name: go_executor + version: << parameters.go_version >> + steps: + - checkout + - restore_cache: + keys: + - go-mod-v4-{{ checksum "go.sum" }} + - run: + name: Install Dependencies + command: go mod download + - save_cache: + key: go-mod-v4-{{ checksum "go.sum" }} + paths: + - "/go/pkg/mod" + - run: + name: Run tests with race detector + command: make test-race + lint: + parameters: + go_version: + type: string + executor: + name: go_executor + version: << parameters.go_version >> + steps: + - checkout + - restore_cache: + keys: + - go-mod-v4-{{ checksum "go.sum" }} + - run: + name: Install Dependencies + command: go mod download + - run: + name: Install tooling + command: | + make tooling + - save_cache: + key: go-mod-v4-{{ checksum "go.sum" }} + paths: + - "/go/pkg/mod" + - run: + name: Linting + command: make lint + - run: + name: Running vulncheck + command: make vuln-check + fmt: + parameters: + go_version: + type: string + executor: + name: go_executor + version: << parameters.go_version >> + steps: + - checkout + - restore_cache: + keys: + - go-mod-v4-{{ checksum "go.sum" }} + - run: + name: Install Dependencies + command: go mod download + - run: + name: Install tooling + command: | + make tooling + - save_cache: + key: go-mod-v4-{{ checksum "go.sum" }} + paths: + - "/go/pkg/mod" + - run: + name: Running formatting + command: | + make fmt + make has-changes + +workflows: + version: 2 + build-and-test: + jobs: + - test: + matrix: + parameters: + go_version: *go-versions + - test-race: + matrix: + parameters: + go_version: *go-versions + - lint: + matrix: + parameters: + go_version: *go-versions + - fmt: + matrix: + parameters: + go_version: *go-versions diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1cfa28cb..00000000 --- a/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -# vim: ft=yaml sw=2 ts=2 - -language: go - -# enable database services -services: - - mysql - - postgresql - -# create test database -before_install: - - mysql -e 'CREATE DATABASE IF NOT EXISTS sqlxtest;' - - psql -c 'create database sqlxtest;' -U postgres - - go get github.com/mattn/goveralls - - export SQLX_MYSQL_DSN="travis:@/sqlxtest?parseTime=true" - - export SQLX_POSTGRES_DSN="postgres://postgres:@localhost/sqlxtest?sslmode=disable" - - export SQLX_SQLITE_DSN="$HOME/sqlxtest.db" - -# go versions to test -go: - - "1.15.x" - - "1.16.x" - -# run tests w/ coverage -script: - - travis_retry $GOPATH/bin/goveralls -service=travis-ci diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..448b9ddd --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +.ONESHELL: +SHELL = /bin/sh +.SHELLFLAGS = -ec + +BASE_PACKAGE := github.com/jmoiron/sqlx + +tooling: + go install honnef.co/go/tools/cmd/staticcheck@v0.4.7 + go install golang.org/x/vuln/cmd/govulncheck@v1.0.4 + go install golang.org/x/tools/cmd/goimports@v0.20.0 + +has-changes: + git diff --exit-code --quiet HEAD -- + +lint: + go vet ./... + staticcheck -checks=all ./... + +fmt: + go list -f '{{.Dir}}' ./... | xargs -I {} goimports -local $(BASE_PACKAGE) -w {} + +vuln-check: + govulncheck ./... + +test-race: + go test -v -race -count=1 ./... + +update-dependencies: + go get -u -t -v ./... + go mod tidy diff --git a/README.md b/README.md index 0d715929..5bfd231a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # sqlx -[![Build Status](https://travis-ci.org/jmoiron/sqlx.svg?branch=master)](https://travis-ci.org/jmoiron/sqlx) [![Coverage Status](https://coveralls.io/repos/github/jmoiron/sqlx/badge.svg?branch=master)](https://coveralls.io/github/jmoiron/sqlx?branch=master) [![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/jmoiron/sqlx) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/jmoiron/sqlx/master/LICENSE) +[![CircleCI](https://dl.circleci.com/status-badge/img/gh/jmoiron/sqlx/tree/master.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/jmoiron/sqlx/tree/master) [![Coverage Status](https://coveralls.io/repos/github/jmoiron/sqlx/badge.svg?branch=master)](https://coveralls.io/github/jmoiron/sqlx?branch=master) [![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/jmoiron/sqlx) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/jmoiron/sqlx/master/LICENSE) sqlx is a library which provides a set of extensions on go's standard `database/sql` library. The sqlx versions of `sql.DB`, `sql.TX`, `sql.Stmt`, diff --git a/doc.go b/doc.go index e2b4e60b..b8010417 100644 --- a/doc.go +++ b/doc.go @@ -8,5 +8,4 @@ // Additions include scanning into structs, named query support, rebinding // queries for different drivers, convenient shorthands for common error handling // and more. -// package sqlx diff --git a/named.go b/named.go index 728aa04d..6ac44777 100644 --- a/named.go +++ b/named.go @@ -174,7 +174,7 @@ func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{ arglist := make([]interface{}, 0, len(names)) // grab the indirected value of arg - v := reflect.ValueOf(arg) + var v reflect.Value for v = reflect.ValueOf(arg); v.Kind() == reflect.Ptr; { v = v.Elem() } diff --git a/named_context.go b/named_context.go index 07ad2165..9ad23f4e 100644 --- a/named_context.go +++ b/named_context.go @@ -1,3 +1,4 @@ +//go:build go1.8 // +build go1.8 package sqlx diff --git a/named_context_test.go b/named_context_test.go index fd1d851b..03f933d9 100644 --- a/named_context_test.go +++ b/named_context_test.go @@ -1,3 +1,4 @@ +//go:build go1.8 // +build go1.8 package sqlx @@ -18,12 +19,12 @@ func TestNamedContextQueries(t *testing.T) { ctx := context.Background() // Check that invalid preparations fail - ns, err = db.PrepareNamedContext(ctx, "SELECT * FROM person WHERE first_name=:first:name") + _, err = db.PrepareNamedContext(ctx, "SELECT * FROM person WHERE first_name=:first:name") if err == nil { t.Error("Expected an error with invalid prepared statement.") } - ns, err = db.PrepareNamedContext(ctx, "invalid sql") + _, err = db.PrepareNamedContext(ctx, "invalid sql") if err == nil { t.Error("Expected an error with invalid prepared statement.") } diff --git a/named_test.go b/named_test.go index 8481b35b..0ee5b85f 100644 --- a/named_test.go +++ b/named_test.go @@ -139,12 +139,12 @@ func TestNamedQueries(t *testing.T) { var err error // Check that invalid preparations fail - ns, err = db.PrepareNamed("SELECT * FROM person WHERE first_name=:first:name") + _, err = db.PrepareNamed("SELECT * FROM person WHERE first_name=:first:name") if err == nil { t.Error("Expected an error with invalid prepared statement.") } - ns, err = db.PrepareNamed("invalid sql") + _, err = db.PrepareNamed("invalid sql") if err == nil { t.Error("Expected an error with invalid prepared statement.") } diff --git a/reflectx/reflect.go b/reflectx/reflect.go index 0b109942..8ec6a138 100644 --- a/reflectx/reflect.go +++ b/reflectx/reflect.go @@ -3,7 +3,6 @@ // allows for Go-compatible named attribute access, including accessing embedded // struct attributes and the ability to use functions and struct tags to // customize field names. -// package reflectx import ( diff --git a/reflectx/reflect_test.go b/reflectx/reflect_test.go index e73af5b1..d0d9be23 100644 --- a/reflectx/reflect_test.go +++ b/reflectx/reflect_test.go @@ -354,7 +354,7 @@ func TestFieldsEmbedded(t *testing.T) { fi = fields.GetByPath("person.name") if fi == nil { - t.Errorf("Expecting person.name to exist") + t.Fatal("Expecting person.name to exist") } if fi.Path != "person.name" { t.Errorf("Expecting %s, got %s", "person.name", fi.Path) @@ -365,7 +365,7 @@ func TestFieldsEmbedded(t *testing.T) { fi = fields.GetByTraversal([]int{1, 0}) if fi == nil { - t.Errorf("Expecting traveral to exist") + t.Fatal("Expecting traversal to exist") } if fi.Path != "name" { t.Errorf("Expecting %s, got %s", "name", fi.Path) @@ -373,7 +373,7 @@ func TestFieldsEmbedded(t *testing.T) { fi = fields.GetByTraversal([]int{2}) if fi == nil { - t.Errorf("Expecting traversal to exist") + t.Fatal("Expecting traversal to exist") } if _, ok := fi.Options["required"]; !ok { t.Errorf("Expecting required option to be set") @@ -642,7 +642,6 @@ func TestMapperMethodsByName(t *testing.T) { A0 *B `db:"A0"` B `db:"A1"` A2 int - a3 int } val := &A{ @@ -847,22 +846,22 @@ func TestMustBe(t *testing.T) { valueErr, ok := r.(*reflect.ValueError) if !ok { t.Errorf("unexpected Method: %s", valueErr.Method) - t.Error("expected panic with *reflect.ValueError") - return + t.Fatal("expected panic with *reflect.ValueError") } if valueErr.Method != "github.com/jmoiron/sqlx/reflectx.TestMustBe" { + t.Fatalf("unexpected Method: %s", valueErr.Method) } if valueErr.Kind != reflect.String { - t.Errorf("unexpected Kind: %s", valueErr.Kind) + t.Fatalf("unexpected Kind: %s", valueErr.Kind) } } else { - t.Error("expected panic") + t.Fatal("expected panic") } }() typ = reflect.TypeOf("string") mustBe(typ, reflect.Struct) - t.Error("got here, didn't expect to") + t.Fatal("got here, didn't expect to") } type E1 struct { diff --git a/sqlx.go b/sqlx.go index f7b28768..8259a4fe 100644 --- a/sqlx.go +++ b/sqlx.go @@ -5,7 +5,6 @@ import ( "database/sql/driver" "errors" "fmt" - "io/ioutil" "path/filepath" "reflect" @@ -51,9 +50,9 @@ func mapper() *reflectx.Mapper { // isScannable takes the reflect.Type and the actual dest value and returns // whether or not it's Scannable. Something is scannable if: -// * it is not a struct -// * it implements sql.Scanner -// * it has no exported fields +// - it is not a struct +// - it implements sql.Scanner +// - it has no exported fields func isScannable(t reflect.Type) bool { if reflect.PtrTo(t).Implements(_scannerInterface) { return true @@ -160,6 +159,8 @@ func mapperFor(i interface{}) *reflectx.Mapper { } var _scannerInterface = reflect.TypeOf((*sql.Scanner)(nil)).Elem() + +//lint:ignore U1000 ignoring this for now var _valuerInterface = reflect.TypeOf((*driver.Valuer)(nil)).Elem() // Row is a reimplementation of sql.Row in order to gain access to the underlying @@ -248,6 +249,8 @@ type DB struct { // NewDb returns a new sqlx DB wrapper for a pre-existing *sql.DB. The // driverName of the original database is required for named query support. +// +//lint:ignore ST1003 changing this would break the package interface. func NewDb(db *sql.DB, driverName string) *DB { return &DB{DB: db, driverName: driverName, Mapper: mapper()} } @@ -884,9 +887,9 @@ func structOnlyError(t reflect.Type) error { // then each row must only have one column which can scan into that type. This // allows you to do something like: // -// rows, _ := db.Query("select id from people;") -// var ids []int -// scanAll(rows, &ids, false) +// rows, _ := db.Query("select id from people;") +// var ids []int +// scanAll(rows, &ids, false) // // and ids will be a list of the id results. I realize that this is a desirable // interface to expose to users, but for now it will only be exposed via changes @@ -935,9 +938,9 @@ func scanAll(rows rowsi, dest interface{}, structOnly bool) error { var values []interface{} var m *reflectx.Mapper - switch rows.(type) { + switch rows := rows.(type) { case *Rows: - m = rows.(*Rows).Mapper + m = rows.Mapper default: m = mapper() } diff --git a/sqlx_context.go b/sqlx_context.go index 7aa4dd01..32621d56 100644 --- a/sqlx_context.go +++ b/sqlx_context.go @@ -1,3 +1,4 @@ +//go:build go1.8 // +build go1.8 package sqlx diff --git a/sqlx_context_test.go b/sqlx_context_test.go index e49ab8b7..91c5cba1 100644 --- a/sqlx_context_test.go +++ b/sqlx_context_test.go @@ -1,15 +1,15 @@ +//go:build go1.8 // +build go1.8 // The following environment variables, if set, will be used: // -// * SQLX_SQLITE_DSN -// * SQLX_POSTGRES_DSN -// * SQLX_MYSQL_DSN +// - SQLX_SQLITE_DSN +// - SQLX_POSTGRES_DSN +// - SQLX_MYSQL_DSN // // Set any of these variables to 'skip' to skip them. Note that for MySQL, // the string '?parseTime=True' will be appended to the DSN if it's not there // already. -// package sqlx import ( @@ -23,9 +23,10 @@ import ( "time" _ "github.com/go-sql-driver/mysql" - "github.com/jmoiron/sqlx/reflectx" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" + + "github.com/jmoiron/sqlx/reflectx" ) func MultiExecContext(ctx context.Context, e ExecerContext, query string) { @@ -92,7 +93,7 @@ func TestMissingNamesContextContext(t *testing.T) { FirstName string `db:"first_name"` LastName string `db:"last_name"` Email string - //AddedAt time.Time `db:"added_at"` + // AddedAt time.Time `db:"added_at"` } // test Select first @@ -485,7 +486,7 @@ func TestNamedQueryContext(t *testing.T) { // these are tests for #73; they verify that named queries work if you've // changed the db mapper. This code checks both NamedQuery "ad-hoc" style // queries and NamedStmt queries, which use different code paths internally. - old := *db.Mapper + old := (*db).Mapper type JSONPerson struct { FirstName sql.NullString `json:"FIRST"` @@ -570,7 +571,7 @@ func TestNamedQueryContext(t *testing.T) { check(t, rows) - db.Mapper = &old + db.Mapper = old // Test nested structs type Place struct { @@ -831,7 +832,7 @@ func TestUsageContext(t *testing.T) { if err != nil { t.Error(err) } - //fmt.Printf("%#v\n%#v\n%#v\n", placesptr[0], placesptr[1], placesptr[2]) + // fmt.Printf("%#v\n%#v\n%#v\n", placesptr[0], placesptr[1], placesptr[2]) // if you have null fields and use SELECT *, you must use sql.Null* in your struct // this test also verifies that you can use either a []Struct{} or a []*Struct{} @@ -1274,9 +1275,9 @@ func TestInContext(t *testing.T) { } RunWithSchemaContext(context.Background(), defaultSchema, t, func(ctx context.Context, db *DB, t *testing.T) { loadDefaultFixtureContext(ctx, db, t) - //tx.MustExecContext(ctx, tx.Rebind("INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)"), "United States", "New York", "1") - //tx.MustExecContext(ctx, tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Hong Kong", "852") - //tx.MustExecContext(ctx, tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Singapore", "65") + // tx.MustExecContext(ctx, tx.Rebind("INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)"), "United States", "New York", "1") + // tx.MustExecContext(ctx, tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Hong Kong", "852") + // tx.MustExecContext(ctx, tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Singapore", "65") telcodes := []int{852, 65} q := "SELECT * FROM place WHERE telcode IN(?) ORDER BY telcode" query, args, err := In(q, telcodes) @@ -1355,7 +1356,7 @@ func TestConn(t *testing.T) { RunWithSchemaContext(context.Background(), schema, t, func(ctx context.Context, db *DB, t *testing.T) { conn, err := db.Connx(ctx) - defer conn.Close() + defer conn.Close() //lint:ignore SA5001 it's OK to ignore this here. if err != nil { t.Fatal(err) } diff --git a/sqlx_test.go b/sqlx_test.go index 1d4aa20d..9fac2cd4 100644 --- a/sqlx_test.go +++ b/sqlx_test.go @@ -1,13 +1,12 @@ // The following environment variables, if set, will be used: // -// * SQLX_SQLITE_DSN -// * SQLX_POSTGRES_DSN -// * SQLX_MYSQL_DSN +// - SQLX_SQLITE_DSN +// - SQLX_POSTGRES_DSN +// - SQLX_MYSQL_DSN // // Set any of these variables to 'skip' to skip them. Note that for MySQL, // the string '?parseTime=True' will be appended to the DSN if it's not there // already. -// package sqlx import ( @@ -23,9 +22,10 @@ import ( "time" _ "github.com/go-sql-driver/mysql" - "github.com/jmoiron/sqlx/reflectx" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" + + "github.com/jmoiron/sqlx/reflectx" ) /* compile time checks that Db, Tx, Stmt (qStmt) implement expected interfaces */ @@ -41,7 +41,6 @@ var TestMysql = true var sldb *DB var pgdb *DB var mysqldb *DB -var active = []*DB{} func init() { ConnectAll() @@ -269,7 +268,7 @@ func TestMissingNames(t *testing.T) { FirstName string `db:"first_name"` LastName string `db:"last_name"` Email string - //AddedAt time.Time `db:"added_at"` + // AddedAt time.Time `db:"added_at"` } // test Select first @@ -659,7 +658,7 @@ func TestNamedQuery(t *testing.T) { // these are tests for #73; they verify that named queries work if you've // changed the db mapper. This code checks both NamedQuery "ad-hoc" style // queries and NamedStmt queries, which use different code paths internally. - old := *db.Mapper + old := (*db).Mapper type JSONPerson struct { FirstName sql.NullString `json:"FIRST"` @@ -744,7 +743,7 @@ func TestNamedQuery(t *testing.T) { check(t, rows) - db.Mapper = &old + db.Mapper = old // Test nested structs type Place struct { @@ -1016,7 +1015,7 @@ func TestUsage(t *testing.T) { if err != nil { t.Error(err) } - //fmt.Printf("%#v\n%#v\n%#v\n", placesptr[0], placesptr[1], placesptr[2]) + // fmt.Printf("%#v\n%#v\n%#v\n", placesptr[0], placesptr[1], placesptr[2]) // if you have null fields and use SELECT *, you must use sql.Null* in your struct // this test also verifies that you can use either a []Struct{} or a []*Struct{} @@ -1578,9 +1577,9 @@ func TestIn(t *testing.T) { } RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) { loadDefaultFixture(db, t) - //tx.MustExec(tx.Rebind("INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)"), "United States", "New York", "1") - //tx.MustExec(tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Hong Kong", "852") - //tx.MustExec(tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Singapore", "65") + // tx.MustExec(tx.Rebind("INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)"), "United States", "New York", "1") + // tx.MustExec(tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Hong Kong", "852") + // tx.MustExec(tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Singapore", "65") telcodes := []int{852, 65} q := "SELECT * FROM place WHERE telcode IN(?) ORDER BY telcode" query, args, err := In(q, telcodes) @@ -1864,11 +1863,11 @@ func TestIn130Regression(t *testing.T) { } t.Log(args) for _, a := range args { - switch a.(type) { + switch a := a.(type) { case string: t.Log("ok: string", a) case *string: - t.Error("ng: string pointer", a, *a.(*string)) + t.Error("ng: string pointer", a, *a) } } }) @@ -1883,11 +1882,11 @@ func TestIn130Regression(t *testing.T) { } t.Log(args) for _, a := range args { - switch a.(type) { + switch a := a.(type) { case string: t.Log("ok: string", a) case *string: - t.Error("ng: string pointer", a, *a.(*string)) + t.Error("ng: string pointer", a, *a) } } }) diff --git a/types/doc.go b/types/doc.go new file mode 100644 index 00000000..a3cca5c7 --- /dev/null +++ b/types/doc.go @@ -0,0 +1,4 @@ +// Package types provides some useful types which implement the `sql.Scanner` +// and `driver.Valuer` interfaces, suitable for use as scan and value targets with +// database/sql. +package types diff --git a/types/types.go b/types/types.go index 808f5834..fe4c634b 100644 --- a/types/types.go +++ b/types/types.go @@ -6,7 +6,6 @@ import ( "database/sql/driver" "encoding/json" "errors" - "io/ioutil" ) @@ -36,6 +35,7 @@ func (g *GzippedText) Scan(src interface{}) error { case []byte: source = src default: + //lint:ignore ST1005 changing this could break consumers of this package return errors.New("Incompatible type for GzippedText") } reader, err := gzip.NewReader(bytes.NewReader(source)) @@ -102,6 +102,7 @@ func (j *JSONText) Scan(src interface{}) error { case nil: *j = emptyJSON default: + //lint:ignore ST1005 changing this could break consumers of this package return errors.New("Incompatible type for JSONText") } *j = append((*j)[0:0], source...) diff --git a/types/types_test.go b/types/types_test.go index 29813d1e..721d35e4 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -35,7 +35,7 @@ func TestJSONText(t *testing.T) { } j = JSONText(`{"foo": 1, invalid, false}`) - v, err = j.Value() + _, err = j.Value() if err == nil { t.Errorf("Was expecting invalid json to fail!") }