ORM Lens

DOL041 — Planner setting overridden in raw SQL

Default severity: warning · Applicability: unsafe · Category: raw SQL

Detects SET / SET LOCAL / SET SESSION of a planner-toggling Postgres GUC inside application code — enable_* (hashjoin, mergejoin, nestloop, seqscan, indexscan, …), plan_cache_mode, and jit*. The check is pure text shape, so it sees the GUC name even when the value is a bind parameter (%s or %(name)s): cursor.execute('SET enable_seqscan = %s', ['off']) is flagged exactly like a literal.

Why this is a warning and not a style nit

SET enable_hashjoin = off does not make a query faster. It removes the planner’s ability to choose otherwise — and the planner is choosing per query, with row estimates the call site cannot see. The failure mode is asymmetric:

A plain SET is also connection-scoped. With connection pooling, the override leaks into whatever runs next on that same connection. SET LOCAL scopes it to the transaction and is the only form with an excuse — but even that is a judgement call a reviewer should see, so it is flagged too.

What is flagged

cursor.execute('SET enable_seqscan = %s; SET enable_bitmapscan = %s', ['off', 'off'])
cursor.execute("SET LOCAL enable_hashjoin = off")
connection.cursor().execute("SET SESSION enable_bitmapscan TO 'off'")
cursor.execute("SET plan_cache_mode = force_generic_plan")
cursor.execute("SET jit = off")

Each GUC in a multi-statement string is reported separately, and both the = and TO forms match.

What is not flagged

SET search_path, SET timezone, SET statement_timeout, SET work_mem and the rest of the session-settings surface — those have legitimate per-request uses, and flagging them would drown the rule. Only the family that exists to overrule the planner is in scope.

Comment text is ignored as well: Python # comments, SQL -- comments and SQL /* ... */ blocks (including ones spanning several lines) do not produce findings, so commented-out SQL stays quiet.

Bad

def open_tickets(connection):
    # every later query on this pooled connection inherits the override
    connection.cursor().execute("SET enable_hashjoin = off")
    return Ticket.objects.raw(TICKET_REPORT_SQL)

Good

def open_tickets():
    return Ticket.objects.raw(TICKET_REPORT_SQL)

Fix the query or the index instead — or, if an override is truly required, scope it with SET LOCAL inside a transaction and keep it auditable:

with transaction.atomic(), connection.cursor() as cur:
    cur.execute("SET LOCAL enable_seqscan = off")
    ...

Suppress

# orm-lens-disable-next-line DOL041

Or per-workspace in .vscode/settings.json: {"djangoOrmLens.rules": {"DOL041": "off"}}.