How do you prevent SQL injection with database/sql? Why can't placeholders be used for table names or ORDER BY columns?
Always pass user values as arguments to QueryContext, ExecContext or QueryRowContext, and never build SQL with fmt.Sprintf or +. The driver sends the query and the parameters separately (or escapes them correctly), so a value can never become SQL syntax. Placeholder syntax depends on the driver: ? for MySQL and SQLite, $1 for Postgres (pgx, lib/pq), @p1 for SQL Server.
Placeholders stand for values only. The server has to parse and plan the statement before binding, so identifiers such as table names, column names, ORDER BY targets and ASC/DESC are part of the statement's structure and cannot be bound. ORDER BY $1 either errors or sorts by a constant, which means it does nothing. The fix is an allowlist that maps user input to identifiers you control:
var sortCols = map[string]string{"name": "name", "created": "created_at"}
func listUsers(ctx context.Context, db *sql.DB, sortKey string, desc bool, limit int) (*sql.Rows, error) {
col, ok := sortCols[sortKey]
if !ok {
col = "created_at"
}
dir := "ASC"
if desc {
dir = "DESC"
}
q := fmt.Sprintf("SELECT id, name FROM users ORDER BY %s %s LIMIT $1", col, dir)
return db.QueryContext(ctx, q, limit) // values still parameterized
}
// value lookup: always parameterized
var name string
err := db.QueryRowContext(ctx, "SELECT name FROM users WHERE email = $1", email).Scan(&name)
Gotchas: IN (...) lists need one generated placeholder per element (or = ANY($1) with an array in Postgres). LIKE input also needs % and _ escaped. Always defer rows.Close() and check rows.Err().
More on More Standard Library Essentials
- Q535Compare math/rand, math/rand/v2 and crypto/rand. What changed about seeding in Go 1.20 and 1.22, and when must you use crypto/rand?
- Q536text/template vs html/template: how does contextual auto-escaping prevent XSS, and what does template.HTML bypass?
- Q538Why should you compare secrets with crypto/subtle.ConstantTimeCompare instead of ==? How do you hash passwords in Go?
- Q539How does regexp (RE2) differ from PCRE engines? Why doesn't Go support backreferences, and what are the performance guarantees?
- Q540How do you run external commands safely with os/exec? Explain CommandContext, capturing stdout/stderr, exit codes, and why shell injection is not the default risk.
- Q541How do you build a TCP server with net.Listen? Handle per-connection goroutines, read/write deadlines, and graceful shutdown of the listener.