{"record":{"id":"0ac8fe372b4b5275","repo":"apache/druid","slug":"replacement-must-be-a-string-literal","errorCode":null,"errorMessage":"replacement must be a string literal","messagePattern":"replacement must be a string literal","errorType":"validation","errorClass":"DruidException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/expression/RegexpReplaceExprMacro.java","lineNumber":93,"sourceCode":"    private final Expr arg;\n    private final Pattern pattern;\n    private final String replacement;\n\n    private RegexpReplaceExpr(List<Expr> args)\n    {\n      super(args);\n\n      final Expr patternExpr = args.get(1);\n      final Expr replacementExpr = args.get(2);\n\n      if (!ExprUtils.isStringLiteral(patternExpr)\n          && !(patternExpr.isLiteral() && patternExpr.getLiteralValue() == null)) {\n        throw validationFailed(\"pattern must be a string literal\");\n      }\n\n      if (!ExprUtils.isStringLiteral(replacementExpr)\n          && !(replacementExpr.isLiteral() && replacementExpr.getLiteralValue() == null)) {\n        throw validationFailed(\"replacement must be a string literal\");\n      }\n\n      final String patternString = (String) patternExpr.getLiteralValue();\n\n      this.arg = args.get(0);\n      this.pattern = patternString != null ? RegexpExprUtils.compilePattern(patternString, FN_NAME) : null;\n      this.replacement = (String) replacementExpr.getLiteralValue();\n    }\n\n    @Nonnull\n    @Override\n    public ExprEval<?> eval(final ObjectBinding bindings)\n    {\n      if (pattern == null || replacement == null) {\n        return ExprEval.ofString(null);\n      }\n\n      final String s = arg.eval(bindings).asString();","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/expression/RegexpReplaceExprMacro.java#L75-L111","documentation":"regexp_replace requires the replacement argument (third argument) to be a string literal, or an explicit literal NULL. Non-literal replacements cannot be embedded in the precompiled replace expression, so validation fails.","triggerScenarios":"REGEXP_REPLACE(expr, 'pattern', repl) where repl is a column, expression, or non-string non-null literal.","commonSituations":"Replacement strings pulled from other columns; passing numbers or booleans as the replacement; templated query generation injecting non-literal values.","solutions":["Inline the replacement as a quoted string literal","Use literal NULL only if you intend null replacement semantics","Perform substitution with a different expression (e.g. CONCAT) if the replacement must be dynamic"],"exampleFix":"// before\nREGEXP_REPLACE(col, '[aeiou]', repl_col)\n// after\nREGEXP_REPLACE(col, '[aeiou]', '-')","handlingStrategy":"validation","validationCode":"boolean ok = ExprUtils.isStringLiteral(replacementExpr)\n    || (replacementExpr.isLiteral() && replacementExpr.getLiteralValue() == null);\nif (!ok) {\n  throw new IllegalArgumentException(\"replacement must be a string literal\");\n}","typeGuard":"static boolean isStringOrNullLiteral(Expr e) {\n  return ExprUtils.isStringLiteral(e) || (e.isLiteral() && e.getLiteralValue() == null);\n}","tryCatchPattern":"try {\n  return macro.apply(args);\n} catch (ExpressionValidationException e) {\n  log.error(\"regexp_replace replacement invalid: %s\", e.getMessage());\n  throw e;\n}","preventionTips":["Inline replacement strings","Do not reference columns in the replacement position","Add expression validation to query-submission pipelines"],"tags":["druid","regex","literal"],"backgroundTag":"invalid-argument-value","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}