PostgreSQL 2F005 오류 원인과 해결 방법 완벽 가이드

2F005
2026년 06월 27일 | DBMS Error 가이드

이 글에서 다루는 내용

2F005 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.

2F005 function executed no return statement 는?

PostgreSQL 에러 코드 2F005는 PL/pgSQL 또는 기타 절차적 언어로 작성된 함수가 실행되었으나, 함수가 값을 반환해야 하는 상황임에도 불구하고 RETURN 문을 실행하지 않고 종료된 경우 발생합니다. 즉, 함수의 반환 타입이 void가 아닌데 모든 실행 경로에서 명시적인 반환 값을 제공하지 않았을 때 이 에러가 트리거됩니다. 특히 조건 분기(IF/ELSIF/ELSE)가 복잡하게 얽혀 있는 함수에서 특정 분기에 RETURN 문이 누락되어 있을 때 런타임에 이 오류가 발생하며, 컴파일 단계가 아닌 실행 단계에서 감지된다는 점이 디버깅을 까다롭게 만듭니다.


주요 발생 원인

1. 조건 분기에서 RETURN 문 누락

가장 흔한 원인으로, IF/ELSIF/ELSE 구문을 사용할 때 모든 분기에 RETURN 문을 작성하지 않는 경우입니다. 예를 들어 IF 블록과 ELSIF 블록에만 RETURN이 있고 ELSE 블록이 없거나, ELSE 블록 내에 RETURN이 빠진 경우 특정 조건에서 함수가 반환 문 없이 끝나게 됩니다. PostgreSQL은 컴파일 시점에 이를 항상 감지하지 못하므로, 실제로 그 경로가 실행될 때까지 에러가 발생하지 않아 운영 중 예상치 못한 장애로 이어질 수 있습니다.

2. EXCEPTION 블록에서 RETURN 문 누락

BEGIN ... EXCEPTION ... END 구조를 사용하는 함수에서, EXCEPTION 절 내부에 RETURN 문을 작성하지 않는 경우입니다. 정상 경로(BEGIN 블록)에는 RETURN이 있지만, 예외가 발생하여 EXCEPTION 블록으로 제어가 넘어갔을 때 해당 블록이 값을 반환하지 않고 끝나면 2F005 에러가 발생합니다. 예외 처리 로직을 추가할 때 반환 처리를 빠뜨리는 실수는 초보자뿐 아니라 숙련된 개발자도 자주 범하는 오류입니다.

3. 루프나 동적 SQL 분기에서의 RETURN 누락

FOR, WHILE, LOOP 등의 반복문을 사용하거나 동적 SQL(EXECUTE)을 통해 조건별로 다른 처리를 하는 함수에서, 루프가 한 번도 실행되지 않거나 동적 쿼리 결과가 없을 때 함수가 반환 문 없이 종료되는 경우입니다. 특히 FOR rec IN SELECT ... LOOP ... RETURN rec; END LOOP; 패턴에서 SELECT 결과가 없으면 루프 자체가 실행되지 않아 함수가 아무것도 반환하지 못하고 끝납니다. 이런 케이스는 개발 환경에서 데이터가 항상 존재하기 때문에 테스트를 통과하고 운영 환경에서야 비로소 에러가 드러나는 경우가 많습니다.


해결 방법

원인 1 해결: 모든 조건 분기에 RETURN 추가

문제가 있는 코드:

CREATE OR REPLACE FUNCTION get_user_grade(p_score INT)
RETURNS TEXT AS $$
BEGIN
    IF p_score >= 90 THEN
        RETURN 'A';
    ELSIF p_score >= 80 THEN
        RETURN 'B';
    ELSIF p_score >= 70 THEN
        RETURN 'C';
    -- ELSE 분기 없음 → p_score가 70 미만이면 2F005 발생!
    END IF;
END;
$$ LANGUAGE plpgsql;

수정된 코드:

CREATE OR REPLACE FUNCTION get_user_grade(p_score INT)
RETURNS TEXT AS $$
BEGIN
    IF p_score >= 90 THEN
        RETURN 'A';
    ELSIF p_score >= 80 THEN
        RETURN 'B';
    ELSIF p_score >= 70 THEN
        RETURN 'C';
    ELSE
        RETURN 'F'; -- 모든 분기에 RETURN 추가
    END IF;
END;
$$ LANGUAGE plpgsql;

-- 또는 함수 마지막에 기본 반환값을 보장하는 방법
CREATE OR REPLACE FUNCTION get_user_grade(p_score INT)
RETURNS TEXT AS $$
DECLARE
    v_grade TEXT := 'F'; -- 기본값 설정
BEGIN
    IF p_score >= 90 THEN
        v_grade := 'A';
    ELSIF p_score >= 80 THEN
        v_grade := 'B';
    ELSIF p_score >= 70 THEN
        v_grade := 'C';
    END IF;
    RETURN v_grade; -- 함수 끝에 단일 RETURN으로 처리
END;
$$ LANGUAGE plpgsql;

원인 2 해결: EXCEPTION 블록에 RETURN 추가

문제가 있는 코드:

CREATE OR REPLACE FUNCTION safe_divide(p_a NUMERIC, p_b NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
    RETURN p_a / p_b;
EXCEPTION
    WHEN division_by_zero THEN
        RAISE WARNING '0으로 나눌 수 없습니다.';
        -- RETURN 누락! → 2F005 발생
END;
$$ LANGUAGE plpgsql;

수정된 코드:

CREATE OR REPLACE FUNCTION safe_divide(p_a NUMERIC, p_b NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
    RETURN p_a / p_b;
EXCEPTION
    WHEN division_by_zero THEN
        RAISE WARNING '0으로 나눌 수 없습니다. NULL을 반환합니다.';
        RETURN NULL; -- 예외 블록에도 반드시 RETURN 추가
    WHEN OTHERS THEN
        RAISE WARNING '알 수 없는 오류: %', SQLERRM;
        RETURN NULL; -- OTHERS 절에도 RETURN 처리
END;
$$ LANGUAGE plpgsql;

원인 3 해결: 루프 후 기본 반환값 보장

문제가 있는 코드:

CREATE OR REPLACE FUNCTION get_first_active_user(p_dept_id INT)
RETURNS TEXT AS $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN
        SELECT username FROM users
        WHERE dept_id = p_dept_id AND is_active = TRUE
        ORDER BY created_at
    LOOP
        RETURN rec.username; -- 데이터가 없으면 이 줄이 실행되지 않음 → 2F005 발생
    END LOOP;
    -- 루프 이후 RETURN 없음!
END;
$$ LANGUAGE plpgsql;

수정된 코드:

CREATE OR REPLACE FUNCTION get_first_active_user(p_dept_id INT)
RETURNS TEXT AS $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN
        SELECT username FROM users
        WHERE dept_id = p_dept_id AND is_active = TRUE
        ORDER BY created_at
    LOOP
        RETURN rec.username;
    END LOOP;

    -- 루프가 한 번도 실행되지 않은 경우 기본값 반환
    RETURN NULL; -- 또는 RETURN ''; 또는 적절한 기본값
END;
$$ LANGUAGE plpgsql;

-- 더 나은 방법: SELECT INTO를 사용하여 명확하게 처리
CREATE OR REPLACE FUNCTION get_first_active_user(p_dept_id INT)
RETURNS TEXT AS $$
DECLARE
    v_username TEXT;
BEGIN
    SELECT username INTO v_username
    FROM users
    WHERE dept_id = p_dept_id AND is_active = TRUE
    ORDER BY created_at
    LIMIT 1;

    RETURN v_username; -- 결과가 없으면 자동으로 NULL 반환
END;
$$ LANGUAGE plpgsql;

예방 방법

1. 함수 작성 시 단일 출구 패턴(Single Exit Point) 적용

복잡한 조건 분기가 있는 함수는 중간에 여러 RETURN을 사용하는 대신, 결과를 변수에 저장하고 함수의 마지막에 단 하나의 RETURN 문만 사용하는 패턴을 권장합니다. 이렇게 하면 어떤 경로로 실행되더라도 반드시 RETURN 문을 통과하게 되어 2F005 에러를 구조적으로 예방할 수 있습니다. 또한 코드 가독성과 유지보수성도 크게 향상됩니다.

-- 권장 패턴: 단일 출구(Single Exit Point)
CREATE OR REPLACE FUNCTION calculate_bonus(p_emp_id INT)
RETURNS NUMERIC AS $$
DECLARE
    v_salary     NUMERIC;
    v_bonus      NUMERIC := 0; -- 기본값 반드시 설정
    v_dept       TEXT;
BEGIN
    SELECT salary, department INTO v_salary, v_dept
    FROM employees WHERE emp_id = p_emp_id;

    IF v_dept = 'SALES' THEN
        v_bonus := v_salary * 0.2;
    ELSIF v_dept = 'TECH' THEN
        v_bonus := v_salary * 0.15;
    ELSIF v_dept = 'HR' THEN
        v_bonus := v_salary * 0.1;
    END IF;

    RETURN v_bonus; -- 단 하나의 RETURN만 사용
END;
$$ LANGUAGE plpgsql;

2. pgTAP 또는 자동화된 테스트로 경계값 케이스 검증

함수 배포 전에 pgTAP과 같은 PostgreSQL 전용 단위 테스트 프레임워크를 사용하여 모든 조건 분기와 예외 경로를 반드시 테스트해야 합니다. 특히 데이터가 없는 경우, 예외가 발생하는 경우, 경계값(boundary value)에 해당하는 케이스를 자동화 테스트 시나리오에 포함시켜 CI/CD 파이프라인에서 배포 전 자동으로 검증하도록 구성하는 것이 실무에서 매우 효과적입니다.

-- pgTAP을 이용한 간단한 테스트 예시
BEGIN;
SELECT plan(4);

SELECT is(get_user_grade(95), 'A', '95점은 A 등급');
SELECT is(get_user_grade(85), 'B', '85점은 B 등급');
SELECT is(get_user_grade(75), 'C', '75점은 C 등급');
SELECT is(get_user_grade(60), 'F', '60점은 F 등급 (경계값 테스트)');

SELECT * FROM finish();
ROLLBACK;

관련 에러

  • 2F002 (modifying SQL data not permitted): PL/SQL 함수 내에서 허용되지 않는 데이터 변경을 시도할 때 발생하며, 2F005와 함께 PL/pgSQL 함수 실행 관련 에러 클래스(2F)에 속합니다.
  • 42P13 (invalid_function_definition): 함수 정의 자체가 잘못된 경우 발생하는 에러로, RETURNS 절 없이 함수를 생성하거나 반환 타입 불일치 시 발생합니다. 2F005가 런타임 에러라면 이쪽은 컴파일 타임 에러에 해당합니다.
  • P0004 (assert_failure): ASSERT 문을 사용하는 함수에서 조건이 거짓일 때 발생하며, 함수 내부 로직 검증 실패와 관련된 에러입니다. 복잡한 함수 디버깅 시 2F005와 함께 마주치는 경우가 많습니다.

DBMS 에러 코드 시리즈

주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.

본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.

댓글 남기기