Skip to content

Raw SQL

Ahmad Faiz Kamaludin edited this page Oct 18, 2026 · 2 revisions

When the builder cannot express something, drop to raw SQL. This is the one place where quill cannot protect you, so read the rules.

On this page: Expressions · Placeholders · Raw methods · Raw statements · Safety rules

Expressions

db.Raw creates an Expression: SQL text plus bind arguments. Use it as a value:

Where("created_at", ">", db.Raw("now() - interval ?", "1 day"))
Set("updated_at", db.Raw("now()"))
Values(map[string]any{"created_at": db.Raw("now()")})

Placeholders

In raw text:

  • ? is a bind marker. quill translates it to $n on PostgreSQL.
  • ?? is a literal ?.
  • Markers inside quoted strings or quoted identifiers are left alone.

Raw methods

Method Purpose
SelectRaw(sql, args...) Raw select expression
FromRaw(sql, args...) Raw table source
WhereRaw / OrWhereRaw Raw condition
HavingRaw / OrHavingRaw Raw HAVING condition
GroupByRaw(sql) Raw grouping
OrderByRaw(sql) Raw ordering
WithRaw(name, db.Raw(...)) Raw CTE body
Lock(sql) Raw lock clause
JoinClause.WhereRaw / OrWhereRaw Raw condition inside a join

Raw statements

For whole statements with your dialect's placeholders ($1 or ?):

var rows []map[string]any
err := conn.Query(ctx, &rows, "select * from users where age > $1", 30)

n, err := conn.Exec(ctx, "update users set active = $1 where last_seen < $2", false, cutoff)

Query scans like Get. Exec returns rows affected. Hooks fire for both.

Safety rules

Raw SQL text is inserted exactly as written. Never build it from user input. Pass user values as arguments (?), never by string concatenation.

// WRONG: user input becomes SQL
q.WhereRaw("name = '" + name + "'")

// RIGHT: the value is an argument
q.WhereRaw("lower(name) = ?", strings.ToLower(name))
q.Where("name", name)

Risk area: injection through trusted-text APIs. The full list of affected methods and the review checklist are in Security.

Clone this wiki locally