Tencent/WeKnora · error
locking clauses (FOR UPDATE, etc.) are not allowed
Error message
locking clauses (FOR UPDATE, etc.) are not allowed
What it means
validateSelectStmt rejects SELECT statements with a non-empty LockingClause, i.e. row-locking modifiers like FOR UPDATE, FOR SHARE, FOR NO KEY UPDATE, or FOR KEY SHARE. These clauses change transactional/locking semantics rather than just reading data, so the validator disallows them to keep validated queries side-effect-free. The check is unconditional (len(stmt.LockingClause) > 0).
Source
Thrown at internal/utils/inject.go:1363
// Check for UNION/INTERSECT/EXCEPT (compound queries)
if stmt.Op != pg_query.SetOperation_SETOP_NONE {
return fmt.Errorf("compound queries (UNION/INTERSECT/EXCEPT) are not allowed")
}
// Check for WITH clause (CTEs)
if v.checkCTEs && stmt.WithClause != nil {
return fmt.Errorf("WITH clause (CTEs) is not allowed")
}
// Check for INTO clause (SELECT INTO)
if stmt.IntoClause != nil {
return fmt.Errorf("SELECT INTO is not allowed")
}
// Check for LOCKING clause (FOR UPDATE, etc.)
if len(stmt.LockingClause) > 0 {
return fmt.Errorf("locking clauses (FOR UPDATE, etc.) are not allowed")
}
// Validate FROM clause
for _, fromItem := range stmt.FromClause {
if err := v.validateFromItem(fromItem, tablesInQuery, result); err != nil {
return err
}
}
// Validate target list (SELECT columns)
for _, target := range stmt.TargetList {
if err := v.validateNode(target, result); err != nil {
return err
}
}
// Validate WHERE clause
if stmt.WhereClause != nil {View on GitHub (pinned to 988cbb0330)
Solutions
- Remove the locking clause for the validated read path and apply FOR UPDATE in the transactional layer instead.
- Use a transactional execution path (e.g. db.Exec within a transaction) that skips this read-only validator for locking queries.
- Split the flow: validate the plain SELECT, then issue the locking variant directly through the DB driver.
Example fix
// before (rejected)
q := "SELECT * FROM jobs WHERE id = $1 FOR UPDATE"
// after: validate the plain query, lock in the transaction
validate("SELECT * FROM jobs WHERE id = $1")
tx.Exec("SELECT * FROM jobs WHERE id = $1 FOR UPDATE", id) Defensive patterns
Strategy: validation
Validate before calling
re := regexp.MustCompile(`(?i)\bFOR\s+(UPDATE|SHARE|NO\s+KEY\s+UPDATE|KEY\s+SHARE)\b`)
if re.MatchString(sql) {
return fmt.Errorf("locking clause rejected; run this query on the transactional path, not the validated read path")
} Prevention
- Reserve FOR UPDATE for explicit transaction code that bypasses the read-only validator.
- Keep validation and execution paths aligned: validate what you validate-run, transact what you transact-run.
- Watch ORM/QueryBuilder options that auto-append locking clauses.
- Grep code review diffs for FOR UPDATE/FOR SHARE in read paths.
When it happens
Trigger: Calling the validation API with "SELECT * FROM users WHERE id = $1 FOR UPDATE" (typical in SELECT-then-UPDATE transaction patterns); the parse tree has LockingClause entries.
Common situations: ORM query builders that append FOR UPDATE for pessimistic locking inside transactions; developer copies a transactional query into the read-path validator; queue/debounce job implementations using FOR UPDATE SKIP LOCKED.
Related errors
- compound queries (UNION/INTERSECT/EXCEPT) are not allowed
- WITH clause (CTEs) is not allowed
- SELECT INTO is not allowed
- invite code has expired
- join request not found
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/8d1157416289a5cd.
Report an issue: GitHub.