When PostgreSQL raises this error, the message in your logs or psql session will look like:
ERROR: prohibited statement attempted in a non-volatile function
SQLSTATE: 2F003
The condition name is prohibited_sql_statement_attempted and the SQLSTATE is 2F003. It occurs when a SQL-language function (or a function body evaluated under SQL execution rules) attempts a statement that its declared volatility category forbids.
What This Error Means
SQLSTATE class 2F is "SQL Routine Exception," which covers errors arising specifically from the execution of SQL-invokable routines — functions and procedures defined in the SQL language. Error 2F003 is one of several conditions in this class, sitting alongside 2F002 (MODIFYING_SQL_DATA_NOT_PERMITTED) and 2F004 (READING_SQL_DATA_NOT_PERMITTED).
PostgreSQL classifies functions by their volatility category: VOLATILE, STABLE, or IMMUTABLE. These categories carry strict behavioral guarantees:
IMMUTABLE— the function cannot access the database at all and always returns the same result for the same inputs.STABLE— the function may read the database but must not modify it within a single transaction scan.VOLATILE— the function may read and modify the database freely (the default).
When a function is declared IMMUTABLE or STABLE, the planner may call it at plan time, cache its results, or inline it into queries. To enforce these guarantees, PostgreSQL checks whether the statements inside the function body are compatible with the declared volatility. A 2F003 error surfaces when the runtime behavior contradicts what was declared — for example, a data-modification statement (INSERT, UPDATE, DELETE, TRUNCATE) is executed inside a function not declared VOLATILE.
The error terminates the current statement. The surrounding transaction is left in an aborted state and must be rolled back before any further work can proceed.
Common Causes
DML inside an
IMMUTABLEorSTABLEfunction. The most direct cause: a function body contains anINSERT,UPDATE,DELETE, orTRUNCATEbut the function is declaredSTABLEorIMMUTABLE. PostgreSQL enforces the purity contract at execution time.Calling a volatile function from within an
IMMUTABLEorSTABLEfunction. Even if your outer function contains no DML directly, calling another function that isVOLATILE(or that itself modifies data) from inside aSTABLE/IMMUTABLEfunction can trigger this error.Transaction control statements inside a non-
VOLATILESQL function. Statements such asCOMMIT,ROLLBACK,SAVEPOINT, orSET TRANSACTIONare prohibited in functions that are notVOLATILE— and in some contexts are prohibited even inVOLATILESQL-language functions (they are only permitted in procedures called viaCALL).Incorrect volatility declaration during function creation. A developer marks a function
IMMUTABLEfor performance reasons without fully understanding the restriction, then adds DML logic to it later — or the function relies on a sequence (nextval) which is inherently volatile.
How to Fix prohibited_sql_statement_attempted
Change the function's volatility to
VOLATILE. If the function legitimately needs to modify data, drop theSTABLEorIMMUTABLEdeclaration:CREATE OR REPLACE FUNCTION update_audit_log(record_id int) RETURNS void LANGUAGE sql VOLATILE -- explicitly volatile, or omit: VOLATILE is the default AS $$ INSERT INTO audit_log (record_id, logged_at) VALUES (record_id, now()); $$;Remove the DML from the function and restructure the logic. If the function must remain
STABLEorIMMUTABLE(e.g., it is used in an index expression or aCHECKconstraint), move all data-modification logic to a separateVOLATILEfunction or to the calling context:-- Keep this STABLE for use in index expressions CREATE OR REPLACE FUNCTION compute_score(val numeric) RETURNS numeric LANGUAGE sql STABLE AS $$ SELECT val * 1.15; $$; -- Separate VOLATILE function handles writes CREATE OR REPLACE FUNCTION record_and_score(val numeric) RETURNS numeric LANGUAGE sql VOLATILE AS $$ INSERT INTO score_log (val, computed_at) VALUES (val, now()); SELECT compute_score(val); $$;Replace sequences with deterministic logic in
IMMUTABLEfunctions. Any call tonextval(),currval(), or random-number generators inside anIMMUTABLEfunction will trigger this error. Replace with deterministic computation or move the sequence call outside:-- WRONG: nextval is volatile -- CREATE OR REPLACE FUNCTION bad_id() RETURNS bigint LANGUAGE sql IMMUTABLE AS $$ SELECT nextval('my_seq'); $$; -- RIGHT: generate the ID before calling the function SELECT nextval('my_seq') AS new_id, compute_score(42);Use
PROCEDUREwithCALLfor transaction control. If you needCOMMIT/ROLLBACKinside a routine, convert it to a procedure (introduced in PostgreSQL 11) and invoke it withCALLrather than as a function in a query:CREATE OR REPLACE PROCEDURE batch_process() LANGUAGE plpgsql AS $$ BEGIN -- ... DML ... COMMIT; -- ... more DML ... END; $$; CALL batch_process();
Additional Information
- SQLSTATE class
2F(SQL Routine Exception) contains several related conditions:2F000(generic),2F002(MODIFYING_SQL_DATA_NOT_PERMITTED),2F003(PROHIBITED_SQL_STATEMENT_ATTEMPTED), and2F004(READING_SQL_DATA_NOT_PERMITTED). The precise code raised depends on what the function tried to do versus what its purity level allows. - PostgreSQL introduced
STABLEandIMMUTABLEvolatility categories early in its history; the purity enforcement and these SQLSTATE codes have been present since at least PostgreSQL 8.x. - Most client drivers (libpq, psycopg2, JDBC) surface this as a
PSQLException(Java) orpsycopg2.errors.ProhibitedSqlStatementAttemptedwith the2F003SQLSTATE. ORMs typically wrap it in their own exception hierarchy but preserve the underlying SQLSTATE. - Functions declared
IMMUTABLEcan be used in index expressions (CREATE INDEX ... ON t (f(col))). A2F003error during index creation or rebuild usually means the function is incorrectly declared.
Frequently Asked Questions
Why does PostgreSQL only raise this error at runtime and not at function creation time? PostgreSQL performs a limited static check when a function is created or replaced, but it cannot always detect violations statically — especially when the function calls other functions whose volatility might be lower than expected, or when conditional code paths exist. The runtime check is the authoritative enforcement point.
Can an IMMUTABLE function ever write to the database?
No. IMMUTABLE is the strictest volatility level. It signals to the planner that the function is a pure mathematical transformation. Any attempt to write data — or even call a volatile function — violates this contract and will raise 2F003 (or a related 2F error).
Is STABLE safe for functions that call now() or current_timestamp?
Yes, with a nuance. STABLE functions are allowed to read the database and may use now() because PostgreSQL treats the transaction start time as constant within a statement. However, they still cannot modify data. Using clock_timestamp() (which changes within a statement) inside a STABLE function is technically incorrect by semantics, though PostgreSQL may not always raise an error for it.
My function is VOLATILE but I am still getting 2F003 — why?
This can happen if the function is invoked in a context that imposes additional restrictions, such as inside a CHECK constraint expression, a generated column definition, a partial index predicate, or a view that is accessed by a rule rewrite. In those contexts, even VOLATILE functions may be restricted to read-only operations.