The PostgreSQL 2F005 error occurs when a function declared to return a value executes without reaching a RETURN statement. This happens in PL/pgSQL functions that miss return paths, have conditional logic without default returns, or encounter exceptions before returning.
The "2F005: function_executed_no_return_statement" error in PostgreSQL indicates that a function declared with a return type (not VOID) completed execution without encountering any RETURN statement. PostgreSQL requires that all code paths in a non-void function must eventually reach a RETURN statement that provides a value of the declared return type. This error typically occurs in PL/pgSQL functions where: - Conditional branches (IF/ELSE, CASE) don't all contain RETURN statements - Exception handlers catch errors but don't return a value - The function logic exits early via RAISE EXCEPTION or other means without returning - Loops or recursive calls don't guarantee a return path PostgreSQL validates function execution at runtime, not just at definition time. When a function is called and executes without hitting a RETURN statement, PostgreSQL raises this error to prevent returning undefined or null values when a specific type was promised.
Ensure every possible execution path in your function includes a RETURN statement. For IF/ELSE blocks, make sure both the IF and ELSE branches return values:
-- Problematic: ELSE branch missing RETURN
CREATE FUNCTION get_status(code integer)
RETURNS text AS $$
BEGIN
IF code = 1 THEN
RETURN 'Active';
ELSIF code = 2 THEN
RETURN 'Inactive';
-- Missing ELSE RETURN!
END IF;
END;
$$ LANGUAGE plpgsql;
-- Fixed: All branches return
CREATE FUNCTION get_status(code integer)
RETURNS text AS $$
BEGIN
IF code = 1 THEN
RETURN 'Active';
ELSIF code = 2 THEN
RETURN 'Inactive';
ELSE
RETURN 'Unknown'; -- Added RETURN in ELSE
END IF;
END;
$$ LANGUAGE plpgsql;For CASE statements, ensure all WHEN clauses and the ELSE clause have RETURN statements.
When using EXCEPTION blocks, you must include RETURN statements after handling the exception, or re-raise the error:
-- Problematic: Exception caught but no return
CREATE FUNCTION safe_divide(a numeric, b numeric)
RETURNS numeric AS $$
BEGIN
RETURN a / b;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'Division by zero attempted';
-- Missing RETURN here!
END;
$$ LANGUAGE plpgsql;
-- Fixed: Return a default value after exception
CREATE FUNCTION safe_divide(a numeric, b numeric)
RETURNS numeric AS $$
BEGIN
RETURN a / b;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'Division by zero attempted';
RETURN 0; -- Added RETURN
END;
$$ LANGUAGE plpgsql;
-- Alternative: Re-raise the exception
CREATE FUNCTION safe_divide(a numeric, b numeric)
RETURNS numeric AS $$
BEGIN
RETURN a / b;
EXCEPTION
WHEN division_by_zero THEN
RAISE EXCEPTION 'Cannot divide by zero';
END;
$$ LANGUAGE plpgsql;Remember: After an exception is caught and handled, execution continues after the EXCEPTION block, so you must provide a return value.
Loops must guarantee they exit and reach a RETURN statement. Use explicit RETURN statements inside loops or after loop completion:
-- Problematic: Loop might not exit
CREATE FUNCTION find_first_even(numbers integer[])
RETURNS integer AS $$
DECLARE
i integer;
num integer;
BEGIN
FOREACH num IN ARRAY numbers LOOP
IF num % 2 = 0 THEN
RETURN num; -- This is good
END IF;
END LOOP;
-- Missing RETURN if no even numbers found!
END;
$$ LANGUAGE plpgsql;
-- Fixed: Add RETURN after loop
CREATE FUNCTION find_first_even(numbers integer[])
RETURNS integer AS $$
DECLARE
i integer;
num integer;
BEGIN
FOREACH num IN ARRAY numbers LOOP
IF num % 2 = 0 THEN
RETURN num;
END IF;
END LOOP;
RETURN NULL; -- Added RETURN for no-match case
END;
$$ LANGUAGE plpgsql;For WHILE and FOR loops, ensure the loop condition will eventually be false, or include RETURN statements that break out of the loop.
RAISE EXCEPTION terminates function execution immediately. Any code after RAISE EXCEPTION (including RETURN statements) will never execute:
-- Problematic: RETURN after RAISE EXCEPTION
CREATE FUNCTION validate_age(age integer)
RETURNS boolean AS $$
BEGIN
IF age < 0 THEN
RAISE EXCEPTION 'Age cannot be negative';
END IF;
IF age > 150 THEN
RAISE EXCEPTION 'Age seems unrealistic';
END IF;
RETURN true; -- This won't execute if exceptions are raised
END;
$$ LANGUAGE plpgsql;
-- Fixed: Return before raising exceptions
CREATE FUNCTION validate_age(age integer)
RETURNS boolean AS $$
BEGIN
IF age < 0 THEN
RAISE EXCEPTION 'Age cannot be negative';
RETURN false; -- This line is unreachable!
END IF;
IF age > 150 THEN
RAISE EXCEPTION 'Age seems unrealistic';
RETURN false; -- This line is also unreachable!
END IF;
RETURN true;
END;
$$ LANGUAGE plpgsql;
-- Better: Structure without unreachable code
CREATE FUNCTION validate_age(age integer)
RETURNS boolean AS $$
BEGIN
IF age < 0 OR age > 150 THEN
RAISE EXCEPTION 'Invalid age: %', age;
END IF;
RETURN true;
END;
$$ LANGUAGE plpgsql;Remember: RAISE EXCEPTION stops execution, so any code after it (including in the same conditional block) is unreachable.
For functions returning SETOF (multiple rows), you must use RETURN NEXT or RETURN QUERY, not just RETURN:
-- Problematic: Using RETURN instead of RETURN NEXT
CREATE FUNCTION get_users_active_since(date)
RETURNS SETOF users AS $$
DECLARE
user_record users%ROWTYPE;
BEGIN
FOR user_record IN SELECT * FROM users WHERE last_active >= $1 LOOP
RETURN user_record; -- Wrong: should be RETURN NEXT
END LOOP;
-- Missing RETURN; statement
END;
$$ LANGUAGE plpgsql;
-- Fixed: Using RETURN NEXT correctly
CREATE FUNCTION get_users_active_since(date)
RETURNS SETOF users AS $$
DECLARE
user_record users%ROWTYPE;
BEGIN
FOR user_record IN SELECT * FROM users WHERE last_active >= $1 LOOP
RETURN NEXT user_record; -- Correct
END LOOP;
RETURN; -- Required to signal end of result set
END;
$$ LANGUAGE plpgsql;
-- Using RETURN QUERY
CREATE FUNCTION get_recent_orders(days integer)
RETURNS SETOF orders AS $$
BEGIN
RETURN QUERY
SELECT * FROM orders
WHERE order_date > CURRENT_DATE - days;
RETURN; -- Still needed after RETURN QUERY
END;
$$ LANGUAGE plpgsql;For SETOF functions, you need a final RETURN; statement (without arguments) to signal completion.
Create comprehensive tests to ensure all code paths include RETURN statements:
-- Test function with various inputs
CREATE FUNCTION test_get_status() RETURNS void AS $$
BEGIN
-- Test normal cases
RAISE NOTICE 'Status 1: %', get_status(1);
RAISE NOTICE 'Status 2: %', get_status(2);
-- Test edge case that might miss RETURN
RAISE NOTICE 'Status 999: %', get_status(999);
-- Test NULL input
RAISE NOTICE 'Status NULL: %', get_status(NULL);
END;
$$ LANGUAGE plpgsql;
-- Or use DO block for quick testing
DO $$
BEGIN
PERFORM get_status(1);
PERFORM get_status(2);
PERFORM get_status(999); -- This might reveal missing RETURN
PERFORM get_status(NULL);
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Test failed: %', SQLERRM;
END;
$$;
-- Check function definition for completeness
SELECT prosrc FROM pg_proc WHERE proname = 'your_function_name';Manually trace through each conditional branch and loop to verify all paths lead to RETURN statements. Consider using code review tools or PostgreSQL debugging extensions.
PostgreSQL Function Execution Model:
1. Compilation vs Runtime: PostgreSQL validates function syntax at CREATE time but checks RETURN statement execution at runtime.
2. Control Flow Analysis: PostgreSQL doesn't perform static analysis to guarantee all paths return a value - this is checked during execution.
3. VOID Functions: Functions declared as RETURNS VOID don't need RETURN statements (though they can use RETURN; without value).
4. OUT Parameters: Functions with OUT parameters instead of RETURNS have different return semantics.
Common Pitfalls:
- Nested Conditionals: Deeply nested IF/ELSIF/ELSE chains where one branch might be missed.
- Exception Propagation: When an exception is raised and not caught, the function terminates without reaching RETURN.
- Early Returns in Loops: RETURN inside a loop exits the function immediately, not just the loop.
- RETURN in Exception Handlers: The RETURN in an exception handler applies to the entire function, not just the handler.
Debugging Techniques:
1. Add RAISE NOTICE statements before each RETURN to trace execution path.
2. Use PL/pgSQL debugger extensions if available.
3. Test with NULL inputs and edge cases.
4. Simplify complex functions by breaking them into smaller, testable functions.
Performance Considerations:
- Adding unnecessary RETURN statements doesn't impact performance.
- Complex conditional logic with many branches should be optimized for readability.
- Consider using CASE expressions instead of IF/ELSIF chains for simpler return logic.
Version Notes:
- PostgreSQL 14+ may provide better error messages for missing RETURN statements.
- Some older versions might be less strict about certain edge cases.
ERROR: syntax error at end of input
Syntax error at end of input in PostgreSQL
Bind message supplies N parameters but prepared statement requires M
Bind message supplies N parameters but prepared statement requires M in PostgreSQL
Multidimensional arrays must have sub-arrays with matching dimensions
Multidimensional arrays must have sub-arrays with matching dimensions
ERROR: value too long for type character varying
Value too long for type character varying
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL