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

  1. Remove the locking clause for the validated read path and apply FOR UPDATE in the transactional layer instead.
  2. Use a transactional execution path (e.g. db.Exec within a transaction) that skips this read-only validator for locking queries.
  3. 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

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


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/8d1157416289a5cd. Report an issue: GitHub.