{"record":{"id":"d9844bd20007d6c2","repo":"testcontainers/testcontainers-java","slug":"unclosed-dollar-quoted-string-s","errorCode":null,"errorMessage":"Unclosed dollar quoted string [%s].","messagePattern":"Unclosed dollar quoted string \\[(.+?)\\]\\.","errorType":"exception","errorClass":"ScriptUtils.ScriptParseException","httpStatus":null,"severity":"error","filePath":"modules/database-commons/src/main/java/org/testcontainers/ext/ScriptScanner.java","lineNumber":125,"sourceCode":"                } else if (c == '\\\\') {\n                    escaped = true;\n                } else if (c == quote) {\n                    currentMatch = script.substring(offset, i + 1);\n                    offset = i + 1;\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private boolean matchesDollarQuotedString() {\n        //Matches $<tag>$ .... $<tag>$\n        if (matches(dollarQuotedStringDelimiter)) {\n            String delimiter = currentMatch;\n            int end = script.indexOf(delimiter, offset);\n            if (end < 0) {\n                throw new ScriptUtils.ScriptParseException(\n                    String.format(\"Unclosed dollar quoted string [%s].\", delimiter),\n                    resource\n                );\n            }\n            end += delimiter.length();\n            currentMatch = delimiter + script.substring(offset, end);\n            offset = end;\n            return true;\n        }\n        return false;\n    }\n\n    Lexem next() {\n        if (offset < script.length()) {\n            if (matches(separator)) {\n                return Lexem.SEPARATOR;\n            } else if (matchesSingleLineComment() || matchesMultilineComment()) {\n                return Lexem.COMMENT;","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/testcontainers/testcontainers-java/blob/8e549514e3f01c57d70546fbb8599d138f3903e5/modules/database-commons/src/main/java/org/testcontainers/ext/ScriptScanner.java#L107-L143","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Find the reported delimiter in the script and add its matching closing occurrence.","Check that open/close tags are identical ($fn$ closes with $fn$, not $$).","Validate the script directly in psql to confirm it parses before wiring it into tests."],"exampleFix":"// before (script.sql)\nCREATE FUNCTION f() RETURNS int AS $$ BEGIN RETURN 1; END;\n// after (script.sql)\nCREATE FUNCTION f() RETURNS int AS $$ BEGIN RETURN 1; END $$;","handlingStrategy":"validation","validationCode":"Pattern p = Pattern.compile(\"\\\\$[A-Za-z0-9_]*\\\\$\");\nMatcher m = p.matcher(script);\nif (m.results().count() % 2 != 0) {\n    throw new IllegalStateException(\"Odd number of dollar-quote delimiters in script\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    ScriptUtils.executeDatabaseScript(delegate, path, script);\n} catch (ScriptUtils.ScriptParseException e) {\n    if (e.getMessage().startsWith(\"Unclosed dollar quoted string\")) {\n        // locate the delimiter named in the message and fix the script\n    }\n    throw e;\n}","preventionTips":["Validate PostgreSQL scripts with psql before adding them as init scripts.","Ensure dollar-quote open/close tags are identical strings.","Avoid ad-hoc edits that drop the closing delimiter of function bodies."],"tags":["sql","postgresql","dollar-quoted","script-parsing"],"backgroundTag":"schema-validation-failed","analyzedSha":"8e549514e3f01c57d70546fbb8599d138f3903e5","analyzedAt":"2026-09-12T14:56:41.227Z","contentChangedAt":"2026-09-12T14:56:41.227Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}