Skip to content

Commit fef4549

Browse files
committed
Add gounqvet linter for detecting SELECT * usage
This PR adds gounqvet as a new linter to detect SELECT * usage in SQL queries and SQL builders, encouraging explicit column selection for better performance, maintainability, and API stability. ## Features - **SQL String Detection**: Finds SELECT * in string literals - **SQL Builder Support**: Works with popular builders like Squirrel, GORM - **Smart Pattern Recognition**: Avoids false positives for COUNT(*), system queries - **Highly Configurable**: Extensive configuration options - **Standard Integration**: Supports //nolint:gounqvet directives - **Zero Dependencies**: Built on go/analysis framework ## Configuration Options ```yaml linters-settings: gounqvet: check-sql-builders: true # Enable SQL builder checking ignored-functions: [] # Functions to skip ignored-packages: [] # Packages to ignore allowed-patterns: [] # Regex for acceptable SELECT * ignored-file-patterns: [] # File patterns to ignore ignored-directories: [] # Directories to ignore ``` ## Why Avoid SELECT *? - **Performance**: Reduces unnecessary network bandwidth and memory usage - **Maintainability**: Prevents breakage when schema changes - **Security**: Avoids exposing sensitive columns unintentionally - **API Stability**: Prevents issues when new columns are added ## Examples **Problematic code (detected):** ```go query := "SELECT * FROM users" rows := squirrel.Select("*").From("products") ``` **Good code (not flagged):** ```go query := "SELECT id, name FROM users" rows := squirrel.Select("id", "name").From("products") count := "SELECT COUNT(*) FROM users" // Aggregate functions OK ``` Available since golangci-lint v1.50.0 for maximum compatibility. Repository: https://github.com/MirrexOne/gounqvet
1 parent 7798ec5 commit fef4549

File tree

7 files changed

+140
-0
lines changed

7 files changed

+140
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ require (
148148
codeberg.org/chavacava/garif v0.2.0 // indirect
149149
dev.gaijin.team/go/golib v0.6.0 // indirect
150150
github.com/Masterminds/semver/v3 v3.3.1 // indirect
151+
github.com/MirrexOne/gounqvet v1.1.0
151152
github.com/alfatraining/structtag v1.0.0 // indirect
152153
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
153154
github.com/beorn7/perks v1.0.1 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/config/linters_settings.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ type LintersSettings struct {
250250
Gosec GoSecSettings `mapstructure:"gosec"`
251251
Gosmopolitan GosmopolitanSettings `mapstructure:"gosmopolitan"`
252252
Govet GovetSettings `mapstructure:"govet"`
253+
Gounqvet GounqvetSettings `mapstructure:"gounqvet"`
253254
Grouper GrouperSettings `mapstructure:"grouper"`
254255
Iface IfaceSettings `mapstructure:"iface"`
255256
ImportAs ImportAsSettings `mapstructure:"importas"`
@@ -1083,3 +1084,12 @@ func (s *CustomLinterSettings) Validate() error {
10831084

10841085
return nil
10851086
}
1087+
1088+
type GounqvetSettings struct {
1089+
CheckSQLBuilders bool `mapstructure:"check-sql-builders"`
1090+
IgnoredFunctions []string `mapstructure:"ignored-functions"`
1091+
IgnoredPackages []string `mapstructure:"ignored-packages"`
1092+
AllowedPatterns []string `mapstructure:"allowed-patterns"`
1093+
IgnoredFilePatterns []string `mapstructure:"ignored-file-patterns"`
1094+
IgnoredDirectories []string `mapstructure:"ignored-directories"`
1095+
}

pkg/golinters/gounqvet/gounqvet.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package gounqvet
2+
3+
import (
4+
"golang.org/x/tools/go/analysis"
5+
6+
"github.com/MirrexOne/gounqvet"
7+
pkgconfig "github.com/MirrexOne/gounqvet/pkg/config"
8+
"github.com/golangci/golangci-lint/v2/pkg/config"
9+
"github.com/golangci/golangci-lint/v2/pkg/goanalysis"
10+
)
11+
12+
func New(settings *config.GounqvetSettings) *goanalysis.Linter {
13+
var cfg *pkgconfig.GounqvetSettings
14+
if settings != nil {
15+
cfg = &pkgconfig.GounqvetSettings{
16+
CheckSQLBuilders: settings.CheckSQLBuilders,
17+
IgnoredFunctions: settings.IgnoredFunctions,
18+
IgnoredPackages: settings.IgnoredPackages,
19+
AllowedPatterns: settings.AllowedPatterns,
20+
IgnoredFilePatterns: settings.IgnoredFilePatterns,
21+
IgnoredDirectories: settings.IgnoredDirectories,
22+
}
23+
}
24+
25+
analyzer := gounqvet.NewWithConfig(cfg)
26+
27+
return goanalysis.NewLinter(
28+
"gounqvet",
29+
"Detects SELECT * usage in SQL queries and SQL builders, encouraging explicit column selection",
30+
[]*analysis.Analyzer{analyzer},
31+
nil,
32+
).WithLoadMode(goanalysis.LoadModeTypesInfo)
33+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package gounqvet
2+
3+
import (
4+
"testing"
5+
6+
"github.com/golangci/golangci-lint/v2/pkg/config"
7+
)
8+
9+
func TestGounqvetWithSettings(t *testing.T) {
10+
settings := &config.GounqvetSettings{
11+
CheckSQLBuilders: true,
12+
IgnoredFunctions: []string{"fmt.Printf"},
13+
AllowedPatterns: []string{`SELECT \* FROM information_schema\..*`},
14+
IgnoredDirectories: []string{"vendor"},
15+
IgnoredFilePatterns: []string{"*_test.go"},
16+
}
17+
18+
linter := New(settings)
19+
20+
if linter == nil {
21+
t.Fatal("Expected linter to be created")
22+
}
23+
24+
if linter.Name() != "gounqvet" {
25+
t.Fatalf("Expected linter name 'gounqvet', got '%s'", linter.Name())
26+
}
27+
}
28+
29+
func TestGounqvetNilSettings(t *testing.T) {
30+
linter := New(nil)
31+
32+
if linter == nil {
33+
t.Fatal("Expected linter to be created with nil settings")
34+
}
35+
36+
if linter.Name() != "gounqvet" {
37+
t.Fatalf("Expected linter name 'gounqvet', got '%s'", linter.Name())
38+
}
39+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package testdata
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
)
7+
8+
func badQueries() {
9+
// want "avoid SELECT \\* - explicitly specify needed columns for better performance, maintainability and stability"
10+
query := "SELECT * FROM users"
11+
12+
var db *sql.DB
13+
// want "avoid SELECT \\* - explicitly specify needed columns for better performance, maintainability and stability"
14+
rows, _ := db.Query("SELECT * FROM orders WHERE status = ?", "active")
15+
_ = rows
16+
17+
// This should not trigger because it's a COUNT function
18+
count := "SELECT COUNT(*) FROM users"
19+
_ = count
20+
21+
// This should not trigger because of nolint comment
22+
debug := "SELECT * FROM debug_table" //nolint:gounqvet
23+
_ = debug
24+
25+
// Good queries (should not trigger)
26+
goodQuery := "SELECT id, name, email FROM users"
27+
_ = goodQuery
28+
29+
fmt.Println(query)
30+
}
31+
32+
type SQLBuilder interface {
33+
Select(columns ...string) SQLBuilder
34+
From(table string) SQLBuilder
35+
Where(condition string) SQLBuilder
36+
Query() string
37+
}
38+
39+
func badSQLBuilder(builder SQLBuilder) {
40+
// want "avoid SELECT \\* in SQL builder - explicitly specify columns to prevent unnecessary data transfer and schema change issues"
41+
query := builder.Select("*").From("products")
42+
_ = query
43+
}
44+
45+
func goodSQLBuilder(builder SQLBuilder) {
46+
// Good usage - should not trigger
47+
query := builder.Select("id", "name", "price").From("products")
48+
_ = query
49+
}

pkg/lint/lintersdb/builder_linter.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import (
5656
"github.com/golangci/golangci-lint/v2/pkg/golinters/gosec"
5757
"github.com/golangci/golangci-lint/v2/pkg/golinters/gosmopolitan"
5858
"github.com/golangci/golangci-lint/v2/pkg/golinters/govet"
59+
"github.com/golangci/golangci-lint/v2/pkg/golinters/gounqvet"
5960
"github.com/golangci/golangci-lint/v2/pkg/golinters/grouper"
6061
"github.com/golangci/golangci-lint/v2/pkg/golinters/iface"
6162
"github.com/golangci/golangci-lint/v2/pkg/golinters/importas"
@@ -405,6 +406,11 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) {
405406
WithAutoFix().
406407
WithURL("https://pkg.go.dev/cmd/vet"),
407408

409+
linter.NewConfig(gounqvet.New(&cfg.Linters.Settings.Gounqvet)).
410+
WithSince("v1.50.0").
411+
WithLoadForGoAnalysis().
412+
WithURL("https://github.com/MirrexOne/gounqvet"),
413+
408414
linter.NewConfig(grouper.New(&cfg.Linters.Settings.Grouper)).
409415
WithSince("v1.44.0").
410416
WithURL("https://github.com/leonklingele/grouper"),

0 commit comments

Comments
 (0)