From 1be0d9e3f239af443f8c8c9f6a3a0f7b344b49ca Mon Sep 17 00:00:00 2001 From: Himanshu Rai Date: Thu, 11 Dec 2025 10:40:42 +0530 Subject: [PATCH 1/4] Fix advisory lock Also upgraded Go version and package dependencies. --- go.mod | 10 +++++----- go.sum | 20 ++++++++------------ hyperbun.go | 9 +++++---- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index e87c7ae..1f2a5b2 100644 --- a/go.mod +++ b/go.mod @@ -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 ( @@ -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 ) diff --git a/go.sum b/go.sum index 5e6d819..89ae709 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/hyperbun.go b/hyperbun.go index a4ccb8a..c1dda3f 100644 --- a/hyperbun.go +++ b/hyperbun.go @@ -267,7 +267,8 @@ func Exists[ID string | int](m DB, table string, id ID) (bool, error) { func ExistsBySQL(m DB, table string, query string, args ...interface{}) (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([]interface{}{bun.Ident(table)}, args...)...). Scan(m.Context(), &exists); err != nil { return false, annotate(err, "ExistsBySQL", "table", table) } @@ -332,7 +333,7 @@ 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 @@ -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 { @@ -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") } From 7153d6ae5e90d7ef83fe9aeadfa6eab005e2c6ef Mon Sep 17 00:00:00 2001 From: Himanshu Rai Date: Thu, 11 Dec 2025 11:48:36 +0530 Subject: [PATCH 2/4] Upgrade golangci-lint --- .github/workflows/ci.yml | 17 ++++------------- Makefile | 2 +- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2adef68..aa8421a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 - name: Test run: make test diff --git a/Makefile b/Makefile index 3cd7a2e..54fb47b 100644 --- a/Makefile +++ b/Makefile @@ -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; \ } lint: linters-install From 4c9478cd97fd248ff5ab4657bee01a47d00543c9 Mon Sep 17 00:00:00 2001 From: Himanshu Rai Date: Thu, 11 Dec 2025 12:34:15 +0530 Subject: [PATCH 3/4] Add golangci-lint config --- .github/workflows/ci.yml | 2 +- .golangci.yaml | 484 +++++++++++++++++++++++++++++++++++++++ Makefile | 2 +- hyperbun.go | 46 ++-- hyperbun_test.go | 8 +- internal/underscore.go | 4 +- util.go | 6 +- util_test.go | 2 +- 8 files changed, 521 insertions(+), 33 deletions(-) create mode 100644 .golangci.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa8421a..c72c82f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v9 with: - version: v2.7 + version: v2.7.2 - name: Test run: make test diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..bc0fcfa --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,484 @@ +# This file is licensed under the terms of the MIT license https://opensource.org/license/mit +# Copyright (c) 2021-2025 Marat Reimers + +## Golden config for golangci-lint v2.7.2 +# +# This is the best config for golangci-lint based on my experience and opinion. +# It is very strict, but not extremely strict. +# Feel free to adapt it to suit your needs. +# If this config helps you, please consider keeping a link to this repo (see the next comment). + +# Based on https://github.com/maratori/golangci-lint-config/blob/v2.7.2/.golangci.yml, with following changes +# 1. formatters.enable: Enabled gci, gofumpt; Disabled goimports +# 2. formatters.settings: Added gci; Removed goimports +# 3. linters.enable: Disabled gochecknoglobals, mnd, nilnil, testpackage + +version: "2" + +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 50 + +formatters: + enable: + - gci # checks if code and import statements are formatted, with additional rules + - gofumpt # enforces a stricter format than 'gofmt', while being backwards compatible + - golines # checks if code is formatted, and fixes long lines + + ## you may want to enable + #- goimports # checks if the code and import statements are formatted according to the 'goimports' command + #- gofmt # checks if the code is formatted according to 'gofmt' command + #- swaggo # formats swaggo comments + + # All settings can be found here https://github.com/golangci/golangci-lint/blob/HEAD/.golangci.reference.yml + settings: + gci: + # Section configuration to compare against. + # Section names are case-insensitive and may contain parameters in (). + # The default order of sections is `standard > default > custom > blank > dot > alias > localmodule`. + # If `custom-order` is `true`, it follows the order of `sections` option. + # Default: ["standard", "default"] + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - prefix(github.com/hypersequent/hyperbun) # Custom section: groups all imports with the specified Prefix. + # Checks that no inline comments are present. + # Default: false + no-inline-comments: true + # Checks that no prefix comments (comment lines above an import) are present. + # Default: false + no-prefix-comments: true + + golines: + # Target maximum line length. + # Default: 100 + max-len: 120 + +linters: + enable: + - asasalint # checks for pass []any as any in variadic func(...any) + - asciicheck # checks that your code does not contain non-ASCII identifiers + - bidichk # checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - canonicalheader # checks whether net/http.Header uses canonical header + - copyloopvar # detects places where loop variables are copied (Go 1.22+) + - cyclop # checks function and package cyclomatic complexity + - depguard # checks if package imports are in a list of acceptable packages + - dupl # tool for code clone detection + - durationcheck # checks for two durations multiplied together + - embeddedstructfieldcheck # checks embedded types in structs + - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - exhaustive # checks exhaustiveness of enum switch statements + - exptostd # detects functions from golang.org/x/exp/ that can be replaced by std functions + - fatcontext # detects nested contexts in loops + - forbidigo # forbids identifiers + - funcorder # checks the order of functions, methods, and constructors + - funlen # tool for detection of long functions + - gocheckcompilerdirectives # validates go compiler directive comments (//go:) + #- gochecknoglobals # checks that no global variables exist + - gochecknoinits # checks that no init functions are present in Go code + - gochecksumtype # checks exhaustiveness on Go "sum types" + - gocognit # computes and checks the cognitive complexity of functions + - goconst # finds repeated strings that could be replaced by a constant + - gocritic # provides diagnostics that check for bugs, performance and style issues + - gocyclo # computes and checks the cyclomatic complexity of functions + - godoclint # checks Golang's documentation practice + - godot # checks if comments end in a period + - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod + - goprintffuncname # checks that printf-like functions are named with f at the end + - gosec # inspects source code for security problems + - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - iface # checks the incorrect use of interfaces, helping developers avoid interface pollution + - ineffassign # detects when assignments to existing variables are not used + - intrange # finds places where for loops could make use of an integer range + - iotamixing # checks if iotas are being used in const blocks with other non-iota declarations + - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) + - makezero # finds slice declarations with non-zero initial length + - mirror # reports wrong mirror patterns of bytes/strings usage + #- mnd # detects magic numbers + - modernize # suggests simplifications to Go code, using modern language and library features + - musttag # enforces field tags in (un)marshaled structs + - nakedret # finds naked returns in functions greater than a specified function length + - nestif # reports deeply nested if statements + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - nilnesserr # reports that it checks for err != nil, but it returns a different nil value error (powered by nilness and nilerr) + #- nilnil # checks that there is no simultaneous return of nil error and an invalid value + - noctx # finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - nonamedreturns # reports all named returns + - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL + - perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative + - predeclared # finds code that shadows one of Go's predeclared identifiers + - promlinter # checks Prometheus metrics naming via promlint + - protogetter # reports direct reads from proto message fields when getters should be used + - reassign # checks that package variables are not reassigned + - recvcheck # checks for receiver type consistency + - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint + - rowserrcheck # checks whether Err of rows is checked successfully + - sloglint # ensure consistent code style when using log/slog + - spancheck # checks for mistakes with OpenTelemetry/Census spans + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - staticcheck # is a go vet on steroids, applying a ton of static analysis checks + - testableexamples # checks if examples are testable (have an expected output) + - testifylint # checks usage of github.com/stretchr/testify + #- testpackage # makes you use a separate _test package + - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # removes unnecessary type conversions + - unparam # reports unused function parameters + - unqueryvet # detects SELECT * in SQL queries and SQL builders, encouraging explicit column selection + - unused # checks for unused constants, variables, functions and types + - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - usetesting # reports uses of functions with replacement inside the testing package + - wastedassign # finds wasted assignment statements + - whitespace # detects leading and trailing whitespace + + ## you may want to enable + #- arangolint # opinionated best practices for arangodb client + #- decorder # checks declaration order and count of types, constants, variables and functions + #- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized + #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega + #- godox # detects usage of FIXME, TODO and other keywords inside comments + #- goheader # checks is file header matches to pattern + #- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters + #- interfacebloat # checks the number of methods inside an interface + #- ireturn # accept interfaces, return concrete types + #- noinlineerr # disallows inline error handling `if err := ...; err != nil {` + #- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated + #- tagalign # checks that struct tags are well aligned + #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + #- wrapcheck # checks that errors returned from external packages are wrapped + #- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event + + ## disabled + #- containedctx # detects struct contained context.Context field + #- contextcheck # [too many false positives] checks the function whether use a non-inherited context + #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + #- dupword # [useless without config] checks for duplicate words in the source code + #- err113 # [too strict] checks the errors handling expressions + #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted + #- forcetypeassert # [replaced by errcheck] finds forced type assertions + #- gomodguard # [use more powerful depguard] allow and block lists linter for direct Go module dependencies + #- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase + #- grouper # analyzes expression groups + #- importas # enforces consistent import aliases + #- lll # [replaced by golines] reports long lines + #- maintidx # measures the maintainability index of each function + #- misspell # [useless] finds commonly misspelled English words in comments + #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity + #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test + #- tagliatelle # checks the struct tags + #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers + #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines + #- wsl_v5 # [too strict and mostly code is not more readable] add or remove empty lines + + # All settings can be found here https://github.com/golangci/golangci-lint/blob/HEAD/.golangci.reference.yml + settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 30 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled. + # Default: 0.0 + package-average: 10.0 + + depguard: + # Rules to apply. + # + # Variables: + # - File Variables + # Use an exclamation mark `!` to negate a variable. + # Example: `!$test` matches any file that is not a go test file. + # + # `$all` - matches all go files + # `$test` - matches all go test files + # + # - Package Variables + # + # `$gostd` - matches all of go's standard library (Pulled from `GOROOT`) + # + # Default (applies if no custom rules are defined): Only allow $gostd in all files. + rules: + "deprecated": + # List of file globs that will match this list of settings to compare against. + # By default, if a path is relative, it is relative to the directory where the golangci-lint command is executed. + # The placeholder '${base-path}' is substituted with a path relative to the mode defined with `run.relative-path-mode`. + # The placeholder '${config-path}' is substituted with a path relative to the configuration file. + # Default: $all + files: + - "$all" + # List of packages that are not allowed. + # Entries can be a variable (starting with $), a string prefix, or an exact match (if ending with $). + # Default: [] + deny: + - pkg: github.com/golang/protobuf + desc: Use google.golang.org/protobuf instead, see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules + - pkg: github.com/satori/go.uuid + desc: Use github.com/google/uuid instead, satori's package is not maintained + - pkg: github.com/gofrs/uuid$ + desc: Use github.com/gofrs/uuid/v5 or later, it was not a go module before v5 + "non-test files": + files: + - "!$test" + deny: + - pkg: math/rand$ + desc: Use math/rand/v2 instead, see https://go.dev/blog/randv2 + "non-main files": + files: + - "!**/main.go" + deny: + - pkg: log$ + desc: Use log/slog instead, see https://go.dev/blog/slog + + embeddedstructfieldcheck: + # Checks that sync.Mutex and sync.RWMutex are not used as embedded fields. + # Default: false + forbid-mutex: true + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + + exhaustruct: + # List of regular expressions to match type names that should be excluded from processing. + # Anonymous structs can be matched by '' alias. + # Has precedence over `include`. + # Each regular expression must match the full type name, including package path. + # For example, to match type `net/http.Cookie` regular expression should be `.*/http\.Cookie`, + # but not `http\.Cookie`. + # Default: [] + exclude: + # std libs + - ^net/http.Client$ + - ^net/http.Cookie$ + - ^net/http.Request$ + - ^net/http.Response$ + - ^net/http.Server$ + - ^net/http.Transport$ + - ^net/url.URL$ + - ^os/exec.Cmd$ + - ^reflect.StructField$ + # public libs + - ^github.com/Shopify/sarama.Config$ + - ^github.com/Shopify/sarama.ProducerMessage$ + - ^github.com/mitchellh/mapstructure.DecoderConfig$ + - ^github.com/prometheus/client_golang/.+Opts$ + - ^github.com/spf13/cobra.Command$ + - ^github.com/spf13/cobra.CompletionOptions$ + - ^github.com/stretchr/testify/mock.Mock$ + - ^github.com/testcontainers/testcontainers-go.+Request$ + - ^github.com/testcontainers/testcontainers-go.FromDockerfile$ + - ^golang.org/x/tools/go/analysis.Analyzer$ + - ^google.golang.org/protobuf/.+Options$ + - ^gopkg.in/yaml.v3.Node$ + # Allows empty structures in return statements. + # Default: false + allow-empty-returns: true + + funcorder: + # Checks if the exported methods of a structure are placed before the non-exported ones. + # Default: true + struct-method: false + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 100 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 50 + + gochecksumtype: + # Presence of `default` case in switch statements satisfies exhaustiveness, if all members are not listed. + # Default: true + default-signifies-exhaustive: false + + gocognit: + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 20 + + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be found at https://go-critic.com/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + godoclint: + # List of rules to enable in addition to the default set. + # Default: empty + enable: + # Assert no unused link in godocs. + # https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#no-unused-link + - no-unused-link + + govet: + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `GL_DEBUG=govet golangci-lint run --enable=govet` to see default, all available analyzers, and enabled analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + inamedparam: + # Skips check for interface methods with only a single parameter. + # Default: false + skip-single-param: true + + mnd: + # List of function patterns to exclude from analysis. + # Values always ignored: `time.Date`, + # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, + # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. + # Default: [] + ignored-functions: + - args.Error + - flag.Arg + - flag.Duration.* + - flag.Float.* + - flag.Int.* + - flag.Uint.* + - os.Chmod + - os.Mkdir.* + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets.* + - prometheus.LinearBuckets + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, golines ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + perfsprint: + # Optimizes into strings concatenation. + # Default: true + strconcat: false + + reassign: + # Patterns for global variable names that are checked for reassignment. + # See https://github.com/curioswitch/go-reassign#usage + # Default: ["EOF", "Err.*"] + patterns: + - ".*" + + rowserrcheck: + # database/sql is always checked. + # Default: [] + packages: + - github.com/jmoiron/sqlx + + sloglint: + # Enforce not using global loggers. + # Values: + # - "": disabled + # - "all": report all global loggers + # - "default": report only the default slog logger + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-global + # Default: "" + no-global: all + # Enforce using methods that accept a context. + # Values: + # - "": disabled + # - "all": report all contextless calls + # - "scope": report only if a context exists in the scope of the outermost function + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#context-only + # Default: "" + context: scope + + staticcheck: + # SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks + # Example (to disable some checks): [ "all", "-SA1000", "-SA1001"] + # Default: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"] + checks: + - all + # Incorrect or missing package comment. + # https://staticcheck.dev/docs/checks/#ST1000 + - -ST1000 + # Use consistent method receiver names. + # https://staticcheck.dev/docs/checks/#ST1016 + - -ST1016 + # Omit embedded fields from selector expression. + # https://staticcheck.dev/docs/checks/#QF1008 + - -QF1008 + + usetesting: + # Enable/disable `os.TempDir()` detections. + # Default: false + os-temp-dir: true + + exclusions: + # Log a warning if an exclusion rule is unused. + # Default: false + warn-unused: true + # Predefined exclusion rules. + # Default: [] + presets: + - std-error-handling + - common-false-positives + # Excluding configuration per-path, per-linter, per-text and per-source. + rules: + - source: 'TODO' + linters: [ godot ] + - text: 'should have a package comment' + linters: [ revive ] + - text: 'exported \S+ \S+ should have comment( \(or a comment on this block\))? or be unexported' + linters: [ revive ] + - text: 'package comment should be of the form ".+"' + source: '// ?(nolint|TODO)' + linters: [ revive ] + - text: 'comment on exported \S+ \S+ should be of the form ".+"' + source: '// ?(nolint|TODO)' + linters: [ revive, staticcheck ] + - path: '_test\.go' + linters: + - bodyclose + - dupl + - errcheck + - funlen + - goconst + - gosec + - noctx + - wrapcheck diff --git a/Makefile b/Makefile index 54fb47b..45eceea 100644 --- a/Makefile +++ b/Makefile @@ -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.7; \ + 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 diff --git a/hyperbun.go b/hyperbun.go index c1dda3f..d8e4852 100644 --- a/hyperbun.go +++ b/hyperbun.go @@ -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 } @@ -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) } @@ -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) } @@ -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). @@ -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(). @@ -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). @@ -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). @@ -265,10 +265,10 @@ 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 ? WHERE "+query+")", - append([]interface{}{bun.Ident(table)}, args...)...). + append([]any{bun.Ident(table)}, args...)...). Scan(m.Context(), &exists); err != nil { return false, annotate(err, "ExistsBySQL", "table", table) } @@ -276,7 +276,7 @@ func ExistsBySQL(m DB, table string, query string, args ...interface{}) (bool, e 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...). @@ -324,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...). @@ -336,7 +336,7 @@ func UpdateSQLByID[ID string | int](m DB, table string, id ID, query string, arg 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(). @@ -371,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...). @@ -412,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) @@ -422,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) } @@ -435,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("='") @@ -463,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 diff --git a/hyperbun_test.go b/hyperbun_test.go index f0fa047..f9f5fbd 100644 --- a/hyperbun_test.go +++ b/hyperbun_test.go @@ -1,7 +1,7 @@ package hyperbun import ( - "fmt" + "errors" "testing" "github.com/stretchr/testify/assert" @@ -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='': 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(), ) } diff --git a/internal/underscore.go b/internal/underscore.go index c827fbc..d19f4b6 100644 --- a/internal/underscore.go +++ b/internal/underscore.go @@ -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 len(s) { c := s[i] if IsUpper(c) { if i > 0 && i+1 < len(s) && (IsLower(s[i-1]) || IsLower(s[i+1])) { @@ -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 len(s) { c := s[i] if c == '_' { upperNext = true diff --git a/util.go b/util.go index 67d2150..2edabee 100644 --- a/util.go +++ b/util.go @@ -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 != "" diff --git a/util_test.go b/util_test.go index ca73178..0a4746c 100644 --- a/util_test.go +++ b/util_test.go @@ -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]())) } From 4ed91fe86b54aa0b3569134f83c2777e34de94bb Mon Sep 17 00:00:00 2001 From: Satvik Choudhary Date: Thu, 11 Dec 2025 14:49:36 +0530 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- internal/underscore.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/underscore.go b/internal/underscore.go index d19f4b6..c43d00e 100644 --- a/internal/underscore.go +++ b/internal/underscore.go @@ -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 := range len(s) { + 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])) { @@ -40,7 +40,7 @@ func Underscore(s string) string { func CamelCased(s string) string { r := make([]byte, 0, len(s)) upperNext := true - for i := range len(s) { + for i := range s { c := s[i] if c == '_' { upperNext = true