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

2F005
2026년 08월 31일 | DBMS Error 가이드

이 글에서 다루는 내용

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

2F005 function executed no return statement 는?

PostgreSQL 에러 코드 2F005는 PL/pgSQL(또는 다른 절차적 언어) 함수가 실행되었지만 함수 본문 안에서 RETURN 구문이 실행되지 않았을 때 발생합니다. 즉, 함수가 반환값을 요구하는 구조임에도 불구하고 어떠한 경로로도 RETURN에 도달하지 못한 채 함수가 종료된 상황입니다. 이 에러는 주로 조건 분기(IF/ELSE, CASE)가 복잡한 함수에서 특정 분기에 RETURN이 누락되었거나, 예외 처리 블록 내에서 반환 경로가 빠졌을 때 자주 발생합니다.


주요 발생 원인

1. 조건 분기 중 일부 경로에 RETURN 누락

가장 흔한 원인은 IF/ELSIF/ELSE 구문을 사용할 때 모든 분기에 RETURN을 작성하지 않는 경우입니다. 예를 들어 IF 조건이 참일 때는 값을 반환하지만, 조건이 거짓인 경우의 분기(ELSE 또는 ELSIF)에 RETURN이 없으면 해당 경로로 진입했을 때 2F005 에러가 발생합니다. 실무에서는 조건이 여러 겹으로 중첩될수록 누락 가능성이 높아지므로 각별히 주의해야 합니다.

2. RETURN이 실행되지 않는 예외 처리 구조

BEGIN ... EXCEPTION ... END 블록 내에서 예외가 발생하여 실행 흐름이 EXCEPTION 절로 넘어갔을 때, 해당 절 내에 RETURN이 없으면 함수가 정상적으로 종료되지 않습니다. 개발자들이 예외 처리 블록에서 로그를 남기거나 에러 메시지를 출력하는 것에만 집중하고 반환 구문을 빠뜨리는 경우가 많습니다. 이 상황은 정상 실행 시에는 잘 드러나지 않다가 예외 상황이 처음 발생할 때서야 에러가 나타나므로 디버깅이 까다롭습니다.

3. 복잡한 루프나 동적 SQL에서의 RETURN 누락

FOR 루프나 WHILE 루프, 또는 EXECUTE를 이용한 동적 SQL 실행 후 결과를 반환해야 하는데, 루프가 한 번도 실행되지 않거나 동적 쿼리 결과가 없을 때 반환 경로가 정의되지 않은 경우입니다. 루프가 조건에 따라 실행되지 않을 수 있다는 사실을 간과하고, 루프 내부에만 RETURN을 배치하면 루프가 실행되지 않는 케이스에서 2F005가 발생합니다. 이런 케이스는 데이터 상태에 따라 간헐적으로 발생하기 때문에 더욱 발견하기 어렵습니다.


해결 방법

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

잘못된 예제 (에러 발생):

CREATE OR REPLACE FUNCTION get_discount(price NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
    IF price > 100 THEN
        RETURN price * 0.9;  -- 10% 할인
    ELSIF price > 50 THEN
        RETURN price * 0.95; -- 5% 할인
    -- ELSE 분기 누락! price <= 50 이면 2F005 발생
    END IF;
END;
$$ LANGUAGE plpgsql;

올바른 예제 (수정 후):

CREATE OR REPLACE FUNCTION get_discount(price NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
    IF price > 100 THEN
        RETURN price * 0.9;
    ELSIF price > 50 THEN
        RETURN price * 0.95;
    ELSE
        RETURN price; -- 할인 없음, 모든 분기에 RETURN 보장
    END IF;
END;
$$ LANGUAGE plpgsql;

원인 2 해결: 예외 처리 블록에도 RETURN 추가

잘못된 예제 (에러 발생):

CREATE OR REPLACE FUNCTION safe_divide(a NUMERIC, b NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
    RETURN a / b;
EXCEPTION
    WHEN division_by_zero THEN
        RAISE NOTICE '0으로 나눌 수 없습니다.';
        -- RETURN 누락! 예외 발생 시 2F005 에러
END;
$$ LANGUAGE plpgsql;

올바른 예제 (수정 후):

CREATE OR REPLACE FUNCTION safe_divide(a NUMERIC, b NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
    RETURN a / b;
EXCEPTION
    WHEN division_by_zero THEN
        RAISE NOTICE '0으로 나눌 수 없습니다.';
        RETURN NULL; -- 예외 발생 시 NULL 반환으로 안전하게 처리
END;
$$ LANGUAGE plpgsql;

원인 3 해결: 루프 외부에 기본 RETURN 추가

잘못된 예제 (에러 발생):

CREATE OR REPLACE FUNCTION get_top_salary(dept_id INT)
RETURNS NUMERIC AS $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN
        SELECT salary FROM employees WHERE department_id = dept_id ORDER BY salary DESC LIMIT 1
    LOOP
        RETURN rec.salary; -- 결과가 없으면 루프 미실행 → 2F005 에러
    END LOOP;
    -- 루프 이후 RETURN 없음
END;
$$ LANGUAGE plpgsql;

올바른 예제 (수정 후):

CREATE OR REPLACE FUNCTION get_top_salary(dept_id INT)
RETURNS NUMERIC AS $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN
        SELECT salary FROM employees WHERE department_id = dept_id ORDER BY salary DESC LIMIT 1
    LOOP
        RETURN rec.salary;
    END LOOP;

    -- 루프가 실행되지 않아도 안전하게 NULL 반환
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

동적 SQL을 사용하는 경우의 안전한 패턴

CREATE OR REPLACE FUNCTION dynamic_lookup(tbl_name TEXT, col_name TEXT, lookup_id INT)
RETURNS TEXT AS $$
DECLARE
    result TEXT;
BEGIN
    EXECUTE format('SELECT %I FROM %I WHERE id = $1', col_name, tbl_name)
    INTO result
    USING lookup_id;

    -- 동적 SQL 결과가 없어도 result는 NULL로 설정됨
    RETURN COALESCE(result, 'NOT FOUND');
END;
$$ LANGUAGE plpgsql;

예방 방법

1. 함수 작성 시 RETURN 경로 체크리스트 도입

함수를 작성하거나 리뷰할 때, 모든 가능한 실행 경로(정상 경로, 예외 경로, 빈 결과 경로)에서 반드시 RETURN이 실행되는지를 체크리스트로 확인하는 습관을 들이세요. 특히 팀 코드 리뷰 과정에서 IF/ELSIF/ELSE 분기와 EXCEPTION 블록의 RETURN 유무를 필수 검토 항목으로 지정하면 배포 전에 문제를 잡을 수 있습니다. 아래와 같이 함수 끝에 방어적 RETURN을 항상 추가하는 것도 좋은 습관입니다.

-- 함수 마지막 줄에 항상 방어적 RETURN 추가
CREATE OR REPLACE FUNCTION example_function(input_val INT)
RETURNS TEXT AS $$
BEGIN
    IF input_val > 0 THEN
        RETURN 'positive';
    ELSIF input_val < 0 THEN
        RETURN 'negative';
    END IF;

    -- 방어적 기본 RETURN (모든 케이스 커버)
    RETURN 'zero';
END;
$$ LANGUAGE plpgsql;

2. pgTAP 또는 단위 테스트로 경계값 자동 검증

함수를 배포하기 전에 pgTAP 같은 PostgreSQL 전용 단위 테스트 프레임워크를 활용하여 NULL 입력, 빈 결과셋, 예외 발생 등 경계 조건에 대한 테스트를 자동화하세요. CI/CD 파이프라인에 이 테스트를 포함시키면 코드 변경 시마다 자동으로 모든 실행 경로를 검증할 수 있어 2F005 같은 에러가 운영 환경에 배포되는 것을 사전에 차단할 수 있습니다.

-- pgTAP을 이용한 경계값 테스트 예시
SELECT plan(3);

SELECT is(get_discount(150), 135.0, '150원 상품 → 10% 할인 적용');
SELECT is(get_discount(75),  71.25, '75원 상품 → 5% 할인 적용');
SELECT is(get_discount(30),  30.0,  '30원 상품 → 할인 없음 (ELSE 분기 검증)');

SELECT * FROM finish();

관련 에러

  • 2F000 (sql_routine_exception): PL/SQL 루틴 실행 중 발생하는 일반적인 상위 에러 클래스로, 2F005는 이 클래스의 하위 에러입니다.
  • 2F002 (modifying_sql_data_not_permitted): 읽기 전용 컨텍스트에서 데이터 수정을 시도할 때 발생하며, 함수 속성 설정과 관련이 있습니다.
  • 2F003 (prohibited_sql_statement_attempted): 함수 내에서 허용되지 않는 SQL 구문을 실행할 때 발생합니다.
  • 42P13 (invalid_function_definition): 함수 정의 자체가 잘못된 경우 발생하며, CREATE FUNCTION 시점에 문법 오류를 잡아줍니다. 다만 2F005는 런타임에만 발생하므로 함수 생성 시점에는 감지되지 않는다는 점에서 차이가 있습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기