testcontainers/testcontainers-java · error · ScriptUtils.ScriptParseException

Unclosed dollar quoted string

Error message

Unclosed dollar quoted string [%s].

What it means

ScriptScanner supports PostgreSQL dollar-quoted strings ($tag$...$tag$). When it matches a dollar-quote opening delimiter but cannot find the matching closing delimiter anywhere later in the script, it throws ScriptUtils.ScriptParseException with this message. The dollar-quoted string in the script is never terminated.

Solutions

  1. Find the reported delimiter in the script and add its matching closing occurrence.
  2. Check that open/close tags are identical ($fn$ closes with $fn$, not $$).
  3. Validate the script directly in psql to confirm it parses before wiring it into tests.

Example fix

// before (script.sql)
CREATE FUNCTION f() RETURNS int AS $$ BEGIN RETURN 1; END;
// after (script.sql)
CREATE FUNCTION f() RETURNS int AS $$ BEGIN RETURN 1; END $$;
Defensive patterns

Strategy: validation

Validate before calling

Pattern p = Pattern.compile("\\$[A-Za-z0-9_]*\\$");
Matcher m = p.matcher(script);
if (m.results().count() % 2 != 0) {
    throw new IllegalStateException("Odd number of dollar-quote delimiters in script");
}

Try / catch

try {
    ScriptUtils.executeDatabaseScript(delegate, path, script);
} catch (ScriptUtils.ScriptParseException e) {
    if (e.getMessage().startsWith("Unclosed dollar quoted string")) {
        // locate the delimiter named in the message and fix the script
    }
    throw e;
}

Prevention

When it happens

Trigger: Executing a PostgreSQL init script containing $$...$$ or $fn$...$fn$ where the closing delimiter is missing, or a script whose dollar-quote tags don't match between open and close.

Common situations: Truncated copy-paste of PL/pgSQL function bodies; writing $$ in the script where a different quoting tag was opened; scripts generated programmatically that drop the final delimiter.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of testcontainers/testcontainers-java@8e549514e3 (2026-09-12). Data as JSON: /api/errors/d9844bd20007d6c2. Report an issue: GitHub.

Appendix: source

Thrown at modules/database-commons/src/main/java/org/testcontainers/ext/ScriptScanner.java:125

                } else if (c == '\\') {
                    escaped = true;
                } else if (c == quote) {
                    currentMatch = script.substring(offset, i + 1);
                    offset = i + 1;
                    return true;
                }
            }
        }
        return false;
    }

    private boolean matchesDollarQuotedString() {
        //Matches $<tag>$ .... $<tag>$
        if (matches(dollarQuotedStringDelimiter)) {
            String delimiter = currentMatch;
            int end = script.indexOf(delimiter, offset);
            if (end < 0) {
                throw new ScriptUtils.ScriptParseException(
                    String.format("Unclosed dollar quoted string [%s].", delimiter),
                    resource
                );
            }
            end += delimiter.length();
            currentMatch = delimiter + script.substring(offset, end);
            offset = end;
            return true;
        }
        return false;
    }

    Lexem next() {
        if (offset < script.length()) {
            if (matches(separator)) {
                return Lexem.SEPARATOR;
            } else if (matchesSingleLineComment() || matchesMultilineComment()) {
                return Lexem.COMMENT;

View on GitHub (pinned to 8e549514e3)