Skip to content

Repository files navigation

Query Controller

Static analyzer for detecting true, data-dependent N+1 database query patterns in Go code. The tool traces query parameters backward from GORM-style sinks through interprocedural dataflow and reports cases where a dynamic value reaches a database call inside a loop.

Unlike pattern matchers that flag any query inside a loop, Query Controller uses IFDS-style tabulation to distinguish real N+1 issues (loop-indexed or phi-dependent values flowing into query arguments) from safe constant queries.

For architecture, algorithm details, and contributor onboarding, see onboarding.md.

How it works

Analysis runs in two phases:

  1. Phase 1 — Dataflow trace
    Seeds GORM query sinks (Where, Raw, Query, Exec, etc.) and walks backward through SSA, following assignments, stores, and cross-function parameter/return edges.

  2. Phase 2 — Vulnerability check
    Flags sinks inside loops that also invoke a database execution method (Find, Scan, First, Exec, …) when the traced argument is not a compile-time constant and depends on loop iteration (phi nodes, indexed access, etc.).

  for _, u := range users {
      GetUser(db, u.name)   // loop variable flows into query arg
  }

  func GetUser(db *GormDB, u string) {
      db.Where("it is", u).Find(nil)   // ← reported as N+1
  }

Repository layout

Path Description
analyzer.go, cross_packages.go, internal/ golangci-lint plugin with cross-package fact propagation
onboarding.md Architecture overview and contributor onboarding
code_examples/ Test fixtures for the plugin
plugin/ golangci-lint module plugin registration

The project is a golangci-lint plugin with cross-package summaries (SinkParamFact, ReturnToParamFact, ExecutorFact).

Requirements

golangci-lint plugin

The plugin registers as linter name nplusone.

Build a custom golangci-lint binary

Create a .custom-gcl.yml (see .custom-gcl.yml in the repo root) and build the custom binary:

golangci-lint custom

This produces a custom-gcl binary in the current directory.

Run the linter

./custom-gcl run ./...

Configuration lives in .golangci.yml. Example diagnostic:

🚨 [TRUE N+1] Found dynamic database execution in loop (detected via dataflow)

Tests

Run plugin tests from the project root:

go test ./...

Tests cover scenarios such as base N+1, nested functions, tuple returns, recursion, dynamic query building, and state checking. Fixtures live under code_examples/.

Detected APIs

Query sinks (arguments are traced):

Where, Raw, Not, Or, Select, Having, Group, Order, Query, QueryRow, Exec

Execution methods (must appear in the loop body for a finding):

Scan, Find, First, Take, Last, Pluck, Count, Exec, QueryRow, Query

The standalone CLI also treats print as an execution method for debugging.

Limitations

  • Targets GORM-style method names; other ORMs are not modeled unless they use the same surface API.
  • Cross-package analysis is supported via exported analysis facts.
  • Findings require data dependence on loop iteration; queries in loops with constant arguments are not reported.

License

See repository history for license information.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages