Resolving 42P13 Invalid-Function-Definition Errors During Extension Upgrades
What a 42P13 (invalid_function_definition) error means when an extension update script redefines a function — an incompatible attribute change, a procedural-language validator rejection, or a return-type conflict — and how to categorize it as terminal rather than retryable.
Up: Error Categorization Frameworks — this page is the 42P13 entry in that framework’s taxonomy; the general classifier is described in categorizing extension upgrade errors for automated triage.
Context & When This Applies
A 42P13 surfaces when an extension’s update SQL runs a CREATE OR REPLACE FUNCTION (or a CREATE LANGUAGE) whose definition the server rejects as internally invalid — distinct from a syntax error (42601) or a missing object (42704). It is the error you hit when an update script from a newer extension version assumes a function attribute, language, or return shape that the target server cannot honour. This page applies to PostgreSQL 12–17 and is most common on cross-major upgrades, where the same extension update script runs against a server whose procedural-language set or function-attribute rules differ from where the script was authored.
The defining property of 42P13 is that it is terminal and definitional: the update script itself is asking for something invalid on this server, so retrying re-runs the same rejected CREATE OR REPLACE. That routes it to abort in the classifier, alongside the packaging faults — the fix is a corrected script or a prerequisite the server is missing, established through async upgrade simulation before the promotion ever reaches a window.
Concept: Definitional Validity Is Checked at Replace Time
When an update script issues CREATE OR REPLACE FUNCTION, PostgreSQL validates the new definition against a set of rules that go beyond syntax: the return type cannot change incompatibly, argument names cannot be reassigned in conflicting ways, attribute combinations (IMMUTABLE with a volatile body, PARALLEL SAFE on something that is not) must be coherent, and the target procedural language must exist and its validator must accept the body. A 42P13 means one of those rules failed. Because the check happens at replace time inside the update transaction, the whole ALTER EXTENSION UPDATE aborts and rolls back cleanly — no partial function set is left behind, which distinguishes it from the non-transactional hazards catalogued in ALTER EXTENSION automation.
The categorization consequence is that 42P13 is definitional, not environmental: it will fail identically on every retry against this server, so the classifier treats it as terminal. The remediation is upstream — a corrected update script, an installed prerequisite language, or a target-server version that supports the attribute — surfaced in simulation, exactly where the compatibility validation pipeline is designed to catch it.
Runnable Implementation
The classifier extension below adds a 42P13 rule ahead of the broad 42 drift rules, returning an abort disposition with a hint about which of the three origins fired, parsed from the message.
#!/usr/bin/env python3
"""Classify a 42P13 invalid_function_definition into an actionable sub-reason."""
import re
from dataclasses import dataclass
@dataclass
class P13Verdict:
category: str # INVALID_FUNCTION_DEFINITION
disposition: str # abort
sub_reason: str # attribute | validator | return_type | unknown
fix_hint: str
SUBRULES = [
(r"cannot change return type|cannot change name of input parameter",
"return_type",
"The update script redefines a function signature CREATE OR REPLACE cannot "
"change. Ship a DROP + CREATE in the update script, or an intermediate hop."),
(r"language \".*\" does not exist|could not find language|validator",
"validator",
"The procedural language is missing or its validator rejected the body. "
"Install the language during provisioning, outside the promotion pipeline."),
(r"IMMUTABLE|STABLE|VOLATILE|PARALLEL|SECURITY",
"attribute",
"An attribute combination is invalid on this server. Align the script's "
"function attributes with the target major's rules."),
]
def classify_42p13(sqlstate: str, message: str) -> P13Verdict:
if (sqlstate or "").strip() != "42P13":
raise ValueError("not a 42P13")
for pattern, sub, hint in SUBRULES:
if re.search(pattern, message, re.IGNORECASE):
return P13Verdict("INVALID_FUNCTION_DEFINITION", "abort", sub, hint)
return P13Verdict("INVALID_FUNCTION_DEFINITION", "abort", "unknown",
"Inspect the failing CREATE OR REPLACE in the update script.")
if __name__ == "__main__":
import json
from dataclasses import asdict
v = classify_42p13("42P13",
'ERROR: cannot change return type of existing function')
print(json.dumps(asdict(v), indent=2))
Feed the sub_reason into your triage ticket so the fix is unambiguous, and hold the candidate at the gate until a corrected script passes async upgrade simulation — never let a 42P13 reach a live maintenance window.
Expected Output & Verification
The classifier returns a stable verdict with a concrete fix hint:
{
"category": "INVALID_FUNCTION_DEFINITION",
"disposition": "abort",
"sub_reason": "return_type",
"fix_hint": "The update script redefines a function signature CREATE OR REPLACE cannot change. Ship a DROP + CREATE in the update script, or an intermediate hop."
}
Confirm the diagnosis against the catalog by comparing the existing function’s return type with what the update expects:
-- Existing signature the update is trying to REPLACE incompatibly.
SELECT p.proname,
pg_get_function_result(p.oid) AS returns,
pg_get_function_arguments(p.oid) AS args
FROM pg_proc p
JOIN pg_depend d ON d.objid = p.oid AND d.deptype = 'e'
JOIN pg_extension e ON e.oid = d.refobjid
WHERE e.extname = 'my_extension'
ORDER BY p.proname;
A mismatch between returns and the update’s expected type confirms a return_type 42P13. Record the finding so the extension packager can ship a corrected script, tracked under version control and branching.
Edge Cases & Gotchas
cannot change return type needs a DROP, not a REPLACE. CREATE OR REPLACE FUNCTION refuses to alter a return type:
ERROR: cannot change return type of existing function
HINT: Use DROP FUNCTION my_ext.f(integer) first.
A correct extension update ships an explicit DROP FUNCTION before the CREATE. If you are authoring the fix, add the drop to the update script; if you are only applying it, escalate to the packager rather than hand-dropping, because a manually dropped function orphans its pg_depend link and can trigger a later SCHEMA_DRIFT.
A missing procedural language masquerades as a definition error. An update that installs a function in plpython3u on a server where the language was never created raises a validator-flavoured 42P13. Install the language during provisioning; do not add it to the promotion pipeline, per the boundary in resolving 42501 insufficient-privilege errors.
PARALLEL SAFE on a newer-only attribute fails on older majors. An update script authored against PostgreSQL 16 may set an attribute combination an older target rejects. This is why the same script must be simulated against every server major in the fleet, not just the newest, before the matrix approves the tuple.
The rollback is clean — do not add a snapshot restore reflexively. Because 42P13 aborts inside the update transaction, the catalog is untouched and no snapshot and point-in-time recovery restore is needed. Reserve PITR for the non-transactional failures; a 42P13 just needs a corrected script and a re-run.