Gen also support GORM Clauses, but unlike raw GORM every expression passed toClauses is validated by a security check before it reaches the query builder
(this is a safety guard for the type-safe API).
Upsert
Gen provides compatible Upsert support for different databases like GORM
u := query.User.WithContext(ctx) // query.User exists in gen.WithDefaultQuery mode; |
Note that the generated Save‘s underlying implementation is different from
GORM’s Save: it is exactlyClauses(clause.OnConflict{UpdateAll: true}).Create(values) (the generated
method carries this warning in its own doc comment).
Which clauses are allowed
Every expression passed to Clauses goes through Gen’s security check
(gen.CheckClause) before it reaches the query builder:
| Expression | Constraint |
|---|---|
hints.Hints, hints.IndexHint (gorm.io/hints) |
always allowed |
dbresolver.Operation (gorm.io/plugin/dbresolver) |
always allowed |
clause.OnConflict |
DoUpdates assignments must not use gorm.Expr (OnConflict clause assignment with gorm.Expr is banned for security reasons) |
clause.Locking |
Strength (upper-cased, whitespace-trimmed) only UPDATE or SHARE; Options empty, NOWAIT or SKIP LOCKED (same normalization); Table.Raw must be false |
clause.Insert |
Table.Raw must be false; Modifier must be empty, IGNORE, or <priority> IGNORE where <priority> is LOW_PRIORITY/DELAYED/HIGH_PRIORITY (upper-cased and trimmed; a priority without IGNORE is rejected — the parser reads the first token as the ignore slot) |
any other named clause.Interface whose Name() is VALUES, SELECT, FROM, WHERE, GROUP BY, ORDER BY, LIMIT, UPDATE, SET or DELETE |
banned (clause <name> is banned) — those parts of the statement belong to the type-safe API |
any other named clause.Interface |
accepted (its name is not on the deny-list) |
expressions that are not named clause.Interface values (raw fragments, arbitrary types) |
rejected with unknown clause <value> |
Note the boundary: this is a name deny-list plus a few structural checks, not a
proof that every accepted named clause is safe for your query — pass clauses
from GORM’s own clause package and the supported plugin packages above, not
hand-rolled expressions or raw SQL fragments.
A rejected clause does not panic: the error is attached to the statement
(AddError) and surfaces from the finishing method (Find, Create, …) like
any query error.
Hints
Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with gorm.io/hints, e.g:
import "gorm.io/hints" |
Index hints allow passing index hints to the database in case the query planner gets confused.
import "gorm.io/hints" |
Custom clause validation
query.Use(db, opts...) / query.SetDefault(db, opts...) acceptgen.DOOptions. gen.WithClauseChecker installs a custom validator that runs
before the built-in check:
- return
nil→ the clause is accepted, skipping the built-in check; - return
gen.ErrClauseNotHandled→ fall back to the built-in check; - return any other error → the clause is rejected with your error.
query.SetDefault(db, gen.WithClauseChecker(func(expr clause.Expression) error { |
The built-in rules are also available programmatically asgen.CheckClause(expr) error.
Every generated DO also exposes ReadDB() / WriteDB() to route the statement
through dbresolver read/write pools,
and Returning(value, columns...) for PostgreSQL-style RETURNING.