Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,19 @@ jobs:

steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 2

- uses: actions/setup-go@v5
with:
go-version: '^1.24.0'
go-version: '^1.25.0'
- run: go version

- name: Install gofumpt
run: go install mvdan.cc/gofumpt@latest

- name: Add gofumpt to PATH
run: echo "$GOPATH/bin" >> $GITHUB_PATH

- name: Run gofumpt
run: diff <(echo -n) <(gofumpt -d .)

- name: golangci-lint
uses: golangci/golangci-lint-action@v8
uses: golangci/golangci-lint-action@v9
with:
version: v2.3.1
version: v2.7.2

- name: Test
run: make test
484 changes: 484 additions & 0 deletions .golangci.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ GOCMD=go
linters-install:
@golangci-lint --version >/dev/null 2>&1 || { \
echo "installing linting tools..."; \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.3.1; \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.7.2; \
}

lint: linters-install
Expand Down
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module github.com/hypersequent/hyperbun

go 1.24
go 1.25

require (
github.com/stretchr/testify v1.8.4
github.com/uptrace/bun v1.1.16
github.com/stretchr/testify v1.11.1
github.com/uptrace/bun v1.2.1
)

require (
Expand All @@ -13,8 +13,8 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/sys v0.18.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
20 changes: 8 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
Expand All @@ -10,23 +9,20 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
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/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/uptrace/bun v1.1.16 h1:cn9cgEMFwcyYRsQLfxCRMUxyK1WaHwOVrR3TvzEFZ/A=
github.com/uptrace/bun v1.1.16/go.mod h1:7HnsMRRvpLFUcquJxp22JO8PsWKpFQO/gNXqqsuGWg8=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk=
github.com/uptrace/bun v1.2.1/go.mod h1:cNg+pWBUMmJ8rHnETgf65CEvn3aIKErrwOD6IA8e+Ec=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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=
53 changes: 29 additions & 24 deletions hyperbun.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ type DB interface {
NewUpdate() *bun.UpdateQuery
NewDelete() *bun.DeleteQuery
NewMerge() *bun.MergeQuery
NewRaw(string, ...interface{}) *bun.RawQuery
NewValues(model interface{}) *bun.ValuesQuery
NewRaw(string, ...any) *bun.RawQuery
NewValues(model any) *bun.ValuesQuery
RunInTx(fn func(tx TxContext) error) error
ForceRunInTx(fn func(tx TxContext) error) error
}
Expand Down Expand Up @@ -63,11 +63,11 @@ func (m Context) NewMerge() *bun.MergeQuery {
return m.Bun.NewMerge()
}

func (m Context) NewRaw(query string, args ...interface{}) *bun.RawQuery {
func (m Context) NewRaw(query string, args ...any) *bun.RawQuery {
return m.Bun.NewRaw(query, args...)
}

func (m Context) NewValues(model interface{}) *bun.ValuesQuery {
func (m Context) NewValues(model any) *bun.ValuesQuery {
return m.Bun.NewValues(model)
}

Expand Down Expand Up @@ -121,11 +121,11 @@ func (m TxContext) NewMerge() *bun.MergeQuery {
return m.Bun.NewMerge()
}

func (m TxContext) NewRaw(query string, args ...interface{}) *bun.RawQuery {
func (m TxContext) NewRaw(query string, args ...any) *bun.RawQuery {
return m.Bun.NewRaw(query, args...)
}

func (m TxContext) NewValues(model interface{}) *bun.ValuesQuery {
func (m TxContext) NewValues(model any) *bun.ValuesQuery {
return m.Bun.NewValues(model)
}

Expand Down Expand Up @@ -190,7 +190,7 @@ func TypeByID[T any, ID string | int](m DB, table string, column string, id ID)
return &value, nil
}

func BySQL[T any](m DB, query string, args ...interface{}) (*T, error) {
func BySQL[T any](m DB, query string, args ...any) (*T, error) {
var row T
if err := m.NewSelect().
Model(&row).
Expand All @@ -206,7 +206,7 @@ func BySQL[T any](m DB, query string, args ...interface{}) (*T, error) {
return &row, nil
}

func StructBySQL[T any](m DB, table string, query string, args ...interface{}) (*T, error) {
func StructBySQL[T any](m DB, table string, query string, args ...any) (*T, error) {
var row T
columns := getColumns(reflect.TypeOf(row))
if err := m.NewSelect().
Expand All @@ -224,7 +224,7 @@ func StructBySQL[T any](m DB, table string, query string, args ...interface{}) (
return &row, nil
}

func TypeBySQL[T any](m DB, table string, column string, query string, args ...interface{}) (*T, error) {
func TypeBySQL[T any](m DB, table string, column string, query string, args ...any) (*T, error) {
var value T
if err := m.NewSelect().
ColumnExpr(column).
Expand All @@ -241,7 +241,7 @@ func TypeBySQL[T any](m DB, table string, column string, query string, args ...i
return &value, nil
}

func Many[T any](m DB, query string, args ...interface{}) ([]T, error) {
func Many[T any](m DB, query string, args ...any) ([]T, error) {
var rows []T
if err := m.NewSelect().
Model(&rows).
Expand All @@ -265,17 +265,18 @@ func Exists[ID string | int](m DB, table string, id ID) (bool, error) {
return c == 1, nil
}

func ExistsBySQL(m DB, table string, query string, args ...interface{}) (bool, error) {
func ExistsBySQL(m DB, table string, query string, args ...any) (bool, error) {
var exists bool
if err := m.NewRaw("SELECT EXISTS(SELECT 1 from "+table+" WHERE "+query+")", args...).
if err := m.NewRaw("SELECT EXISTS(SELECT 1 from ? WHERE "+query+")",
append([]any{bun.Ident(table)}, args...)...).
Scan(m.Context(), &exists); err != nil {
return false, annotate(err, "ExistsBySQL", "table", table)
}

return exists, nil
}

func CountQuery(m DB, table string, query string, args ...interface{}) (int, error) {
func CountQuery(m DB, table string, query string, args ...any) (int, error) {
count, err := m.NewSelect().
Table(table).
Where(query, args...).
Expand Down Expand Up @@ -323,7 +324,7 @@ func Update[T any](m DB, row *T, pk ...string) error {
return nil
}

func UpdateSQLByID[ID string | int](m DB, table string, id ID, query string, args ...interface{}) error {
func UpdateSQLByID[ID string | int](m DB, table string, id ID, query string, args ...any) error {
_, err := m.NewUpdate().
Table(table).
Set(query, args...).
Expand All @@ -332,10 +333,10 @@ func UpdateSQLByID[ID string | int](m DB, table string, id ID, query string, arg
if err != nil {
return annotate(err, "UpdateSQLByID", "table", table, "id", id)
}
return err
return nil
}

// To upsert and check multiple constraints, see
// Upsert inserts or updates rows based on conflict columns. To upsert and check multiple constraints, see
// https://stackoverflow.com/questions/35888012/use-multiple-conflict-target-in-on-conflict-clause
func Upsert[T any](m DB, rows T, conflictColumns string) error {
if _, err := m.NewInsert().
Expand All @@ -356,7 +357,7 @@ func UpsertIgnore[T any](m DB, rows T) error {
return annotate(err, "UpsertIgnore", "table", hyperbunTableForType[T]())
}

return err
return nil
}

func DeleteByID[ID string | int](m DB, table string, id ID) error {
Expand All @@ -370,7 +371,7 @@ func DeleteByID[ID string | int](m DB, table string, id ID) error {
return nil
}

func DeleteBySQL(m DB, table string, query string, args ...interface{}) error {
func DeleteBySQL(m DB, table string, query string, args ...any) error {
if _, err := m.NewDelete().
Table(table).
Where(query, args...).
Expand All @@ -397,7 +398,7 @@ 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 {
if err := advisoryLock(tx, id); err != nil {
return annotate(err, "RunInLockedTx")
}

Expand All @@ -411,8 +412,12 @@ func RunInLockedTx(m DB, id string, fn func(tx TxContext) error) error {

func advisoryLock(m DB, id string) error {
h := fnv.New64()
h.Write([]byte(id))
if _, err := h.Write([]byte(id)); err != nil {
return annotate(err, "advisoryLock", "id", id)
}

s := h.Sum64()
//nolint:gosec // PostgreSQL bigint is signed, and we need consistent hashing
if _, err := m.NewRaw("SELECT pg_advisory_xact_lock(?)", int64(s)).
Exec(m.Context()); err != nil {
return annotate(err, "advisoryLock", "id", id)
Expand All @@ -421,7 +426,7 @@ func advisoryLock(m DB, id string) error {
return nil
}

func annotate(err error, op string, kvs ...interface{}) error {
func annotate(err error, op string, kvs ...any) error {
if len(kvs) == 0 {
return fmt.Errorf("performing %s: %w", op, err)
}
Expand All @@ -434,7 +439,7 @@ func annotate(err error, op string, kvs ...interface{}) error {
builder.WriteString(op)

numPairs := len(kvs) / 2
for i := 0; i < numPairs; i++ {
for i := range numPairs {
builder.WriteByte(' ')
builder.WriteString(fmt.Sprint(kvs[i*2]))
builder.WriteString("='")
Expand Down Expand Up @@ -462,13 +467,13 @@ func hyperbunTableForType[T any]() string {
kind = typ.Kind()
}

for i := 0; i < typ.NumField(); i++ {
for i := range typ.NumField() {
f := typ.Field(i)
val, ok := f.Tag.Lookup("bun")
if !ok {
continue
}
for _, ann := range strings.Split(val, ",") {
for ann := range strings.SplitSeq(val, ",") {
spl := strings.Split(ann, ":")
if len(spl) != 2 {
continue
Expand Down
8 changes: 4 additions & 4 deletions hyperbun_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package hyperbun

import (
"fmt"
"errors"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -25,20 +25,20 @@ func TestHyperbunTableForType(t *testing.T) {
func TestAnnotateEven(t *testing.T) {
assert.Equal(t,
"performing TestAnnotate hello='world' id='0': test_error",
annotate(fmt.Errorf("test_error"), "TestAnnotate", "hello", "world", "id", 0).Error(),
annotate(errors.New("test_error"), "TestAnnotate", "hello", "world", "id", 0).Error(),
)
}

func TestAnnotateOdd(t *testing.T) {
assert.Equal(t,
"performing TestAnnotate hello='world' id='0' odd='<missing value>': test_error",
annotate(fmt.Errorf("test_error"), "TestAnnotate", "hello", "world", "id", 0, "odd").Error(),
annotate(errors.New("test_error"), "TestAnnotate", "hello", "world", "id", 0, "odd").Error(),
)
}

func TestAnnotateNoKV(t *testing.T) {
assert.Equal(t,
"performing TestAnnotate: test_error",
annotate(fmt.Errorf("test_error"), "TestAnnotate").Error(),
annotate(errors.New("test_error"), "TestAnnotate").Error(),
)
}
4 changes: 2 additions & 2 deletions internal/underscore.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func ToLower(c byte) byte {
// Underscore converts "CamelCasedString" to "camel_cased_string".
func Underscore(s string) string {
r := make([]byte, 0, len(s)+5)
for i := 0; i < len(s); i++ {
for i := range s {
c := s[i]
if IsUpper(c) {
if i > 0 && i+1 < len(s) && (IsLower(s[i-1]) || IsLower(s[i+1])) {
Expand All @@ -40,7 +40,7 @@ func Underscore(s string) string {
func CamelCased(s string) string {
r := make([]byte, 0, len(s))
upperNext := true
for i := 0; i < len(s); i++ {
for i := range s {
c := s[i]
if c == '_' {
upperNext = true
Expand Down
6 changes: 3 additions & 3 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ import (
)

var (
baseModelType = reflect.TypeOf((*schema.BaseModel)(nil)).Elem()
baseModelType = reflect.TypeFor[schema.BaseModel]()
columnsCache = sync.Map{}
)

func getColumns(typ reflect.Type) []string {
if columns, ok := columnsCache.Load(typ); ok {
return columns.([]string)
return columns.([]string) //nolint:errcheck // we are storing string slice in the cache
}

var columns []string

for i := 0; i < typ.NumField(); i++ {
for i := range typ.NumField() {
f := typ.Field(i)
unexported := f.PkgPath != ""

Expand Down
2 changes: 1 addition & 1 deletion util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ type Entity struct {
}

func TestGetColumns(t *testing.T) {
assert.Equal(t, []string{"id", "name"}, getColumns(reflect.TypeOf(Entity{})))
assert.Equal(t, []string{"id", "name"}, getColumns(reflect.TypeFor[Entity]()))
}