prestodb/presto · error · PrestoException

INVALID_FUNCTION_ARGUMENT

INVALID_FUNCTION_ARGUMENT

Error message

Illegal replacement sequence: 

What it means

regexp_replace wraps RE2/J's Matcher.replaceAll, which throws IllegalArgumentException or IndexOutOfBoundsException when the replacement string uses invalid group references or illegal escape/backreference sequences (e.g., a lone '$' or '$9' when fewer groups exist). Re2JRegexp translates that into an INVALID_FUNCTION_ARGUMENT PrestoException naming the offending replacement string.

Source

Thrown at presto-main-base/src/main/java/com/facebook/presto/type/Re2JRegexp.java:86

        }
        else {
            re2jPatternWithoutDotStartPrefix = re2jPattern;
        }
    }

    public boolean matches(Slice source)
    {
        return re2jPatternWithoutDotStartPrefix.find(source);
    }

    public Slice replace(Slice source, Slice replacement)
    {
        Matcher matcher = re2jPattern.matcher(source);
        try {
            return matcher.replaceAll(replacement);
        }
        catch (IndexOutOfBoundsException | IllegalArgumentException e) {
            throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "Illegal replacement sequence: " + replacement.toStringUtf8());
        }
    }

    public Block extractAll(Slice source, long groupIndex)
    {
        Matcher matcher = re2jPattern.matcher(source);
        int group = toIntExact(groupIndex);
        validateGroup(group, matcher.groupCount());

        BlockBuilder blockBuilder = VARCHAR.createBlockBuilder(null, 32);
        while (true) {
            if (!matcher.find()) {
                break;
            }

            Slice searchedGroup = matcher.group(group);
            if (searchedGroup == null) {
                blockBuilder.appendNull();

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Escape literal '$' in the replacement as '$$' and literal backslashes as '\\'.
  2. Ensure every $n reference is <= the number of capture groups in the pattern (count your parentheses).
  3. If the replacement is user-supplied, sanitize it (e.g., Matcher.quoteReplacement semantics) before passing it to regexp_replace.

Example fix

// before
regexp_replace(name, '(\w+) (\w+)', '$1$3')
// after
regexp_replace(name, '(\w+) (\w+)', '$2 $1')
Defensive patterns

Strategy: validation

Validate before calling

// Java-side sanity check before building the replacement
static String safeReplacement(String repl) {
    // escape literal $ and \ so RE2/J treats them as literals
    return repl.replace("\\", "\\\\").replace("$", "$$");
}

Type guard

boolean hasValidGroupRefs(String replacement, int groupCount) {
    java.util.regex.Matcher m = java.util.regex.Pattern.compile("\\$(\\d+)").matcher(replacement);
    while (m.find()) {
        if (Integer.parseInt(m.group(1)) > groupCount) return false;
    }
    return true;
}

Try / catch

try {
    return regexpReplace(pattern, source, replacement);
} catch (PrestoException e) {
    if (INVALID_FUNCTION_ARGUMENT.equals(e.getErrorCode()) && e.getMessage().startsWith("Illegal replacement sequence")) {
        return regexpReplace(pattern, source, escapedReplacement);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling regexp_replace(pattern, source, replacement) where the replacement Slice contains an invalid group reference (e.g., '$' followed by a digit beyond the group count, unescaped '$', or malformed \ escape) that RE2/J rejects at replace time.

Common situations: Dollar signs in literal replacement text that must be escaped as '$$'; copy-pasted regexes from Java/PCRE engines with group indices larger than the pattern defines; dynamic replacements built from user input.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/49e32bba797581a8. Report an issue: GitHub.