Skip to main content

Production-safe SQL query analyzer for Go

Catch SELECT *, missing WHERE clauses, N+1 loops and slow queries — at runtime through a database/sql driver wrapper, statically in CI, or from the query plan. Think golangci-lint for the SQL your application actually runs.

go get github.com/KARTIKrocks/sqlguard

Driver-layer interception

Wraps database/sql at the driver, so you get a real *sql.DB back and every query is analyzed — including the ones your ORM writes

21 detection rules

SELECT *, DELETE/UPDATE without WHERE, leading wildcards, non-sargable predicates, cartesian joins, deep OFFSETs, and more

N+1 detection

Flags the same query fingerprint repeating inside a window; scope it per request with ResetN1()

Slow-query detection

Latency measured at the driver, reported against a threshold you set

Redaction by default

Literals become ? before a finding leaves the process; every finding carries a PII-free fingerprint safe as a metric label

Quiet in production

Per-finding de-duplication and an exact-query LRU cache — a hot query is parsed once and reported once per window

Static scanner

sqlguard scan ./... walks Go source, resolves constants and fmt.Sprintf formats, and exits 1 for CI

EXPLAIN plan analyzer

Plans a query against live Postgres, MySQL or MariaDB inside a rolled-back read-only transaction — never executes it

Six ORM integrations

GORM, sqlx, native pgx/pgxpool, bun, xorm and ent, all built on the same Guard core with the same options

Opt-in real parsers

Zero-dependency fallback by default; add pgparser or mysqlparser for AST-exact facts without touching the core

One YAML config

A .sqlguard.yml drives the middleware, the scanner and the CLI — disable rules, override severities, tune thresholds

Inline suppressions

-- sqlguard:ignore in SQL or // sqlguard:ignore in Go, per rule or wholesale, honored at runtime and statically

Why sqlguard?

Query logging tells you what ran. It does not tell you that the query was a full-table DELETE, that the same lookup just ran 400 times in a loop, or that a LIKE '%…' can never use the index. sqlguard sits at the one place every query passes through — the database/sql driver — and turns those into findings with a rule name, a redacted query and a fix.

CapabilitysqlguardHand-rolled query logging
Every query analyzed, ORM or raw, no wrapper typeIncludedYou build it
21 SQL anti-pattern rules with tunable severitiesIncludedYou build it
N+1 and slow-query detection at the driverIncludedYou build it
Literal redaction + stable fingerprintsIncludedYou build it
De-duplication and per-query analysis cacheIncludedYou build it
Static scan of Go source for CIIncludedYou build it
EXPLAIN plan analysis that never executesIncludedYou build it
GORM / sqlx / pgx / bun / xorm / ent adaptersIncludedYou build it

Built for the hot path

Analysis runs on every intercepted query, so it has to be cheap. Rule configuration is resolved once at construction, repeated queries hit an exact-string LRU, and a hit is a map lookup with zero allocations. Measured on an Intel i5-11400H (12 cores), Go 1.27, Linux.

Repeated query

20 ns/op

0 allocs — analysis cache hit

First sighting

22 µs/op

full parse + every static rule

Cache speed-up

~1000×

default 1024-entry LRU

Core dependencies

0 third-party

analyzer, middleware, reporter

Reproduce them yourself: go test -bench GuardCheck -benchmem ./middleware/. The repeated-query figure is BenchmarkGuardCheck_Cached, the first-sighting figure BenchmarkGuardCheck_Uncached (cache disabled). Source in middleware/cache_test.go.