PostgreSQL Error 2201B: invalid_regular_expression
PostgreSQL error code 2201B (invalid_regular_expression) is raised when a regular expression pattern passed to a regex-enabled function or operator cannot be parsed by PostgreSQL's internal regex engine. This typically happens when the pattern contains unbalanced brackets, unsupported syntax, or malformed quantifiers. It commonly affects functions like REGEXP_MATCH, REGEXP_REPLACE, REGEXP_SPLIT_TO_TABLE, and operators such as ~ and ~*.
Top 3 Causes
1. Unbalanced Parentheses or Brackets
The most frequent cause is a regex pattern with an opening ( or [ that has no corresponding closing character.
-- Error: unbalanced parentheses
SELECT regexp_match('hello world', '(hello');
-- ERROR: invalid regular expression: parentheses () not balanced
-- Fixed: properly closed group
SELECT regexp_match('hello world', '(hello)');
-- Result: {hello}
-- Error: unbalanced character class
SELECT 'test' ~ '[a-z';
-- ERROR: invalid regular expression: brackets [] not balanced
-- Fixed
SELECT 'test' ~ '[a-z]+';
-- Result: true
2. Unsupported PCRE Syntax
PostgreSQL uses POSIX ERE (Extended Regular Expression), not PCRE. Syntax like lookbehind (?<=...), atomic groups (?>...), or conditional patterns common in Python/JavaScript will not work in PostgreSQL.
-- Error: PCRE lookbehind not supported
SELECT regexp_replace('foobar', '(?<=foo)bar', 'baz');
-- ERROR: invalid regular expression: invalid backreference number
-- Fix: use capturing groups instead
SELECT regexp_replace('foobar', '(foo)(bar)', '\1baz');
-- Result: foobaz
-- Use ~* for case-insensitive matching instead of (?i)
SELECT 'Hello' ~* 'hello';
-- Result: true
3. Invalid Quantifiers or Escape Sequences
Range quantifiers where min exceeds max, or incorrect escape sequences, will trigger this error. In PostgreSQL, \d is not natively supported — use POSIX character classes instead.
-- Error: invalid quantifier range (min > max)
SELECT 'aaa' ~ 'a{3,1}';
-- ERROR: invalid regular expression: invalid repetition count(s)
-- Fixed: correct range
SELECT 'aaa' ~ 'a{1,3}';
-- Result: true
-- Error: \d not supported in standard PostgreSQL regex
SELECT '123' ~ '\d+';
-- May fail or behave unexpectedly
-- Fix: use POSIX character class
SELECT '123' ~ '[0-9]+';
-- Result: true
SELECT '123' ~ '[[:digit:]]+';
-- Result: true
Quick Fix Solutions
Use a safe wrapper function with exception handling to prevent the error from crashing your application:
CREATE OR REPLACE FUNCTION safe_regexp_match(
p_input TEXT,
p_pattern TEXT
)
RETURNS TEXT[] AS $$
BEGIN
RETURN regexp_match(p_input, p_pattern);
EXCEPTION
WHEN invalid_regular_expression THEN
RAISE WARNING 'Invalid regex pattern: %', p_pattern;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- Usage
SELECT safe_regexp_match('hello world', '(hello)\s(\w+)');
-- Result: {hello,world}
SELECT safe_regexp_match('hello', '(broken[');
-- WARNING: Invalid regex pattern: (broken[
-- Result: NULL
Prevention Tips
1. Validate patterns before use with a utility function:
CREATE OR REPLACE FUNCTION is_valid_regex(p_pattern TEXT)
RETURNS BOOLEAN AS $$
BEGIN
PERFORM regexp_match('', p_pattern);
RETURN TRUE;
EXCEPTION
WHEN invalid_regular_expression THEN
RETURN FALSE;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- Test patterns before applying them
SELECT is_valid_regex('^[A-Za-z]+$'); -- true
SELECT is_valid_regex('(unclosed'); -- false
2. Stick to POSIX ERE syntax and avoid PCRE-only features. Always reference the PostgreSQL documentation section 9.7 Pattern Matching when writing regex patterns. Replace \d with [0-9] or [[:digit:]], use ~* for case-insensitive matching instead of (?i), and test all patterns in a development environment before deploying to production. When patterns are dynamically generated from user input, always run them through the is_valid_regex() guard function first.
📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.
Top comments (0)