diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05077ee..2adef68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,13 +17,13 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: - go-version: '^1.22.0' + go-version: '^1.24.0' - run: go version - name: Install gofumpt @@ -36,9 +36,9 @@ jobs: run: diff <(echo -n) <(gofumpt -d .) - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v8 with: - version: v1.56.2 + version: v2.3.1 - name: Test run: make test diff --git a/Makefile b/Makefile index 84079f5..3cd7a2e 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ -GOCMD=GO111MODULE=on go +GOCMD=go linters-install: @golangci-lint --version >/dev/null 2>&1 || { \ echo "installing linting tools..."; \ - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.52.2; \ + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.3.1; \ } lint: linters-install @@ -15,4 +15,4 @@ test: bench: $(GOCMD) test -bench=. -benchmem ./... -.PHONY: test lint linters-install +.PHONY: bench lint linters-install test diff --git a/go.mod b/go.mod index e0e197c..e87c7ae 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,8 @@ module github.com/hypersequent/hyperbun -go 1.21 +go 1.24 require ( - github.com/hypersequent/hyperr v0.0.0-20231126052248-ed921b438e92 github.com/stretchr/testify v1.8.4 github.com/uptrace/bun v1.1.16 ) diff --git a/go.sum b/go.sum index 8a9aad8..5e6d819 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 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/hypersequent/hyperr v0.0.0-20231126052248-ed921b438e92 h1:bgwgm4r2AH5kkARqixxsKOZyrbLJshcYoHUSM0qAp4M= -github.com/hypersequent/hyperr v0.0.0-20231126052248-ed921b438e92/go.mod h1:gB8ywcMBNiPc7TNGNiD/3lXYvLiBzNRYDOiCVL0+p+s= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= diff --git a/hyperbun.go b/hyperbun.go index 95f2318..a4ccb8a 100644 --- a/hyperbun.go +++ b/hyperbun.go @@ -3,14 +3,13 @@ package hyperbun import ( "context" "database/sql" + "errors" "fmt" "hash/fnv" "reflect" "strings" "github.com/uptrace/bun" - - "github.com/hypersequent/hyperr" ) type DB interface { @@ -147,7 +146,7 @@ func ByID[T any, ID string | int](m DB, id ID) (*T, error) { Where("id = ?", id). Limit(1). Scan(m.Context()); err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, annotate(err, "ByID", "table", hyperbunTableForType[T](), "id", id) @@ -165,7 +164,7 @@ func StructByID[T any, ID string | int](m DB, table string, id ID) (*T, error) { Where("id = ?", id). Limit(1). Scan(m.Context(), &row); err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, annotate(err, "StructByID", "table", table, "id", id) @@ -182,7 +181,7 @@ func TypeByID[T any, ID string | int](m DB, table string, column string, id ID) Where("id = ?", id). Limit(1). Scan(m.Context(), &value); err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, annotate(err, "TypeByID", "table", table, "column", column, "id", id) @@ -198,7 +197,7 @@ func BySQL[T any](m DB, query string, args ...interface{}) (*T, error) { Where(query, args...). Limit(1). Scan(m.Context()); err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, annotate(err, "BySQL", "table", hyperbunTableForType[T]()) @@ -216,7 +215,7 @@ func StructBySQL[T any](m DB, table string, query string, args ...interface{}) ( Where(query, args...). Limit(1). Scan(m.Context(), &row); err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, annotate(err, "StructBySQL", "table", table) @@ -233,7 +232,7 @@ func TypeBySQL[T any](m DB, table string, column string, query string, args ...i Where(query, args...). Limit(1). Scan(m.Context(), &value); err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, annotate(err, "TypeBySQL", "table", table, "column", column) @@ -248,7 +247,7 @@ func Many[T any](m DB, query string, args ...interface{}) ([]T, error) { Model(&rows). Where(query, args...). Scan(m.Context()); err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, annotate(err, "Many", "table", hyperbunTableForType[T]()) @@ -384,14 +383,14 @@ func DeleteBySQL(m DB, table string, query string, args ...interface{}) error { func RunInTx(m DB, fn func(tx TxContext) error) error { if err := m.RunInTx(fn); err != nil { - return fmt.Errorf("RunInTx: %w", err) + return annotate(err, "RunInTx") } return nil } func ForceRunInTx(m DB, fn func(tx TxContext) error) error { if err := m.ForceRunInTx(fn); err != nil { - return fmt.Errorf("ForceRunInTx: %w", err) + return annotate(err, "ForceRunInTx") } return nil } @@ -399,51 +398,57 @@ func ForceRunInTx(m DB, fn func(tx TxContext) error) error { func RunInLockedTx(m DB, id string, fn func(tx TxContext) error) error { return RunInTx(m, func(tx TxContext) error { if err := advisoryLock(m, id); err != nil { - return hyperr.Wrap(err) + return annotate(err, "RunInLockedTx") + } + + if err := fn(tx); err != nil { + return annotate(err, "RunInLockedTx", "id", id) } - return fn(tx) + return nil }) } -func advisoryLock(m DB, name string) error { +func advisoryLock(m DB, id string) error { h := fnv.New64() - h.Write([]byte(name)) + h.Write([]byte(id)) s := h.Sum64() if _, err := m.NewRaw("SELECT pg_advisory_xact_lock(?)", int64(s)). Exec(m.Context()); err != nil { - return hyperr.Wrap(err) + return annotate(err, "advisoryLock", "id", id) } return nil } func annotate(err error, op string, kvs ...interface{}) error { - pairs := make([][2]string, len(kvs)/2) + if len(kvs) == 0 { + return fmt.Errorf("performing %s: %w", op, err) + } + + // Use strings.Builder for efficient string concatenation. + // Pre-allocate reasonable capacity to reduce reallocations. + var builder strings.Builder + builder.Grow(len(op) + len(kvs)*20) + + builder.WriteString(op) + numPairs := len(kvs) / 2 - odd := len(kvs)%2 != 0 for i := 0; i < numPairs; i++ { - pairs[i] = [2]string{ - fmt.Sprint(kvs[i*2]), - fmt.Sprint(kvs[i*2+1]), - } + builder.WriteByte(' ') + builder.WriteString(fmt.Sprint(kvs[i*2])) + builder.WriteString("='") + builder.WriteString(fmt.Sprint(kvs[i*2+1])) + builder.WriteByte('\'') } - if odd { - pairs = append(pairs, [2]string{ - fmt.Sprint(kvs[len(kvs)-1]), - "", - }) - } - joined := make([]string, 0, len(pairs)) - for _, pair := range pairs { - joined = append(joined, fmt.Sprint(pair[0], "='", pair[1], "'")) - } - joinedStr := strings.Join(joined, " ") - if joinedStr != "" { - joinedStr = " " + joinedStr + + if len(kvs)%2 != 0 { + builder.WriteByte(' ') + builder.WriteString(fmt.Sprint(kvs[len(kvs)-1])) + builder.WriteString("=''") } - return fmt.Errorf("performing %s%s: %w", op, joinedStr, err) + return fmt.Errorf("performing %s: %w", builder.String(), err) } func hyperbunTableForType[T any]() string {