PostgreSQL 2201B 오류 원인과 해결 방법 완벽 가이드

2201B
2026년 10월 17일 | DBMS Error 가이드

이 글에서 다루는 내용

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

2201B invalid regular expression 는?

PostgreSQL 에러 코드 2201B는 invalid_regular_expression으로, 사용자가 정규 표현식(Regular Expression)을 사용하는 함수나 연산자에 잘못된 패턴을 전달했을 때 발생합니다. 이 에러는 SIMILAR TO, ~, regexp_match(), regexp_replace(), regexp_split_to_table() 등 정규 표현식을 내부적으로 처리하는 모든 PostgreSQL 함수와 연산자에서 발생할 수 있습니다. PostgreSQL은 내부적으로 Henry Spencer의 ARE(Advanced Regular Expressions) 라이브러리를 사용하며, 해당 라이브러리가 파싱할 수 없는 패턴이 입력되면 즉시 이 에러를 반환합니다.


주요 발생 원인

1. 괄호, 브라켓 등 메타문자의 짝 불일치

정규 표현식에서 (, [, { 등의 여는 기호는 반드시 대응하는 닫는 기호 ), ], }와 쌍을 이루어야 합니다. 실무에서는 복잡한 패턴을 동적으로 생성하거나 사용자 입력값을 그대로 정규 표현식에 삽입할 때 이 문제가 자주 발생합니다. 예를 들어 [0-9처럼 닫는 브라켓이 없거나, (abc처럼 닫는 괄호가 누락된 경우가 대표적입니다.

2. 잘못된 수량자(Quantifier) 사용

{n,m} 형태의 수량자는 n이 m보다 크거나, 숫자가 아닌 값이 입력되거나, {와 } 사이에 형식이 맞지 않으면 에러가 발생합니다. 또한 *, +, ? 같은 수량자가 적용될 수 있는 앞 요소(atom) 없이 단독으로 사용되는 경우에도 이 에러가 발생합니다. {3,1}처럼 최솟값이 최댓값보다 큰 경우는 특히 놓치기 쉬운 실수입니다.

3. 잘못된 이스케이프 시퀀스 또는 POSIX 클래스 오타

PostgreSQL의 정규 표현식에서는 \d, \w 등 일부 이스케이프 시퀀스를 지원하지만, 지원하지 않는 시퀀스를 사용하면 에러가 발생합니다. 또한 [:alpha:], [:digit:] 같은 POSIX 문자 클래스는 반드시 [[:alpha:]]처럼 이중 브라켓 안에 넣어야 하는데, [:alpha:]만 단독으로 사용하거나 오타가 있을 경우 이 에러로 이어집니다.


해결 방법

원인 1 해결: 괄호/브라켓 짝 맞추기

잘못된 패턴 예시와 수정 방법입니다.

-- ❌ 에러 발생: 닫는 브라켓 누락
SELECT * FROM users WHERE phone ~ '[0-9';
-- ERROR:  invalid regular expression: brackets ([ ]) not balanced

-- ✅ 수정: 브라켓 닫기
SELECT * FROM users WHERE phone ~ '[0-9]';

-- ❌ 에러 발생: 닫는 괄호 누락
SELECT regexp_match('hello world', '(hello');
-- ERROR:  invalid regular expression: parentheses () not balanced

-- ✅ 수정: 괄호 닫기
SELECT regexp_match('hello world', '(hello)');

-- ❌ 에러 발생: 중괄호 형식 불일치
SELECT * FROM logs WHERE message ~ 'error{';
-- ERROR:  invalid regular expression

-- ✅ 수정: 올바른 수량자 형식 사용
SELECT * FROM logs WHERE message ~ 'error{1,}';

원인 2 해결: 수량자 올바르게 사용하기

-- ❌ 에러: 최솟값 > 최댓값
SELECT regexp_match('aaa', 'a{5,2}');
-- ERROR:  invalid regular expression: invalid repetition count(s)

-- ✅ 수정: 올바른 범위 지정
SELECT regexp_match('aaa', 'a{2,5}');

-- ❌ 에러: 앞에 적용 대상 없이 수량자 단독 사용
SELECT * FROM products WHERE name ~ '*shirt';
-- ERROR:  invalid regular expression: quantifier operand invalid

-- ✅ 수정: 수량자 앞에 적용 대상 추가
SELECT * FROM products WHERE name ~ '.*shirt';

-- ❌ 에러: 빈 수량자
SELECT regexp_replace('abc123', '[0-9]{}', '');
-- ERROR:  invalid regular expression

-- ✅ 수정: 수량자 값 명시
SELECT regexp_replace('abc123', '[0-9]{1,}', '');

원인 3 해결: 이스케이프 및 POSIX 클래스 올바르게 사용하기

-- ❌ 에러: POSIX 클래스 이중 브라켓 누락
SELECT * FROM users WHERE username ~ '[:alpha:]+';
-- ERROR:  invalid regular expression

-- ✅ 수정: 이중 브라켓으로 감싸기
SELECT * FROM users WHERE username ~ '[[:alpha:]]+';

-- ❌ 에러: 지원하지 않는 이스케이프 시퀀스 (주의: \d는 일부 컨텍스트에서 동작하나 명시적 사용 권장)
-- SIMILAR TO에서는 정규식 이스케이프가 다르게 동작
SELECT * FROM users WHERE username SIMILAR TO '\d+';
-- SIMILAR TO는 SQL 표준 패턴을 사용하므로 주의 필요

-- ✅ 수정: ~ 연산자와 올바른 패턴 사용
SELECT * FROM users WHERE username ~ '^[0-9]+$';

-- 실무 예제: 이메일 형식 검증
SELECT email,
       CASE
           WHEN email ~ '^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$'
           THEN '유효한 이메일'
           ELSE '잘못된 이메일'
       END AS validation_result
FROM users;

동적 쿼리에서 안전하게 정규 표현식 검증하기

-- 사용자 입력을 정규 표현식으로 사용하기 전에 유효성 검사하는 함수
CREATE OR REPLACE FUNCTION is_valid_regex(pattern TEXT)
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
BEGIN
    -- 빈 문자열에 대해 패턴 테스트를 시도
    PERFORM '' ~ pattern;
    RETURN TRUE;
EXCEPTION
    WHEN invalid_regular_expression THEN
        RETURN FALSE;
END;
$$;

-- 사용 예시
SELECT is_valid_regex('[0-9]+');    -- true
SELECT is_valid_regex('[0-9');      -- false
SELECT is_valid_regex('a{5,2}');    -- false

-- 실무 활용: 동적 필터링에서 유효하지 않은 패턴 우아하게 처리
DO $$
DECLARE
    user_pattern TEXT := '[invalid';  -- 사용자로부터 입력받은 패턴
    result_count INT;
BEGIN
    IF is_valid_regex(user_pattern) THEN
        EXECUTE format(
            'SELECT COUNT(*) FROM users WHERE name ~ %L',
            user_pattern
        ) INTO result_count;
        RAISE NOTICE '검색 결과: % 건', result_count;
    ELSE
        RAISE NOTICE '유효하지 않은 정규 표현식입니다: %', user_pattern;
    END IF;
END;
$$;

예방 방법

1. 정규 표현식 입력 전 유효성 검증 함수 도입

앞서 소개한 is_valid_regex() 같은 래퍼 함수를 애플리케이션 레이어 또는 데이터베이스 레이어에 반드시 도입하세요. 특히 사용자 입력값을 그대로 정규 표현식 패턴으로 사용하는 경우(예: 검색 필터, 데이터 검증 규칙 동적 생성)에는 반드시 사전 검증 단계를 거쳐야 합니다. 이 방법은 2201B 에러뿐만 아니라 ReDoS(Regular Expression Denial of Service) 공격도 어느 정도 방어하는 효과가 있습니다.

-- CHECK 제약 조건에도 적용 가능
ALTER TABLE search_patterns
    ADD CONSTRAINT chk_valid_pattern
    CHECK (is_valid_regex(pattern_value));

2. 정규 표현식 패턴 테이블 중앙 관리 및 테스트 자동화

프로젝트에서 사용하는 모든 정규 표현식 패턴을 별도의 설정 테이블에 중앙 관리하고, CI/CD 파이프라인 또는 정기 점검 쿼리를 통해 패턴의 유효성을 자동으로 검사하는 체계를 갖추세요. 이렇게 하면 데이터베이스 스키마 변경이나 PostgreSQL 버전 업그레이드 시에도 정규 표현식 호환성 문제를 사전에 발견할 수 있습니다.

-- 정규 표현식 패턴 관리 테이블 예시
CREATE TABLE regex_patterns (
    id          SERIAL PRIMARY KEY,
    name        TEXT NOT NULL UNIQUE,
    pattern     TEXT NOT NULL,
    description TEXT,
    is_valid    BOOLEAN GENERATED ALWAYS AS (is_valid_regex(pattern)) STORED,
    created_at  TIMESTAMPTZ DEFAULT now()
);

-- 유효하지 않은 패턴 모니터링 쿼리
SELECT name, pattern, description
FROM regex_patterns
WHERE is_valid = FALSE;

관련 에러

  • 22025 – invalid_escape_sequence: 정규 표현식 내 이스케이프 문자 처리 시 잘못된 이스케이프 시퀀스가 사용된 경우 발생하며, 2201B와 함께 자주 혼동됩니다.
  • 2201W – invalid_row_count_in_limit_clause: 직접적인 연관은 없으나, 동적 쿼리 생성 과정에서 함께 발생할 수 있는 에러입니다.
  • 42601 – syntax_error: 정규 표현식 자체의 문제가 아니라 SQL 문법 오류로 인해 발생하며, 동적 쿼리 디버깅 시 2201B와 혼동하지 않도록 주의해야 합니다.
  • 22019 – invalid_escape_character: LIKE 또는 SIMILAR TO 패턴에서 잘못된 이스케이프 문자를 지정했을 때 발생합니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기