{"record":{"id":"2ea412e312c1d070","repo":"apache/druid","slug":"unit-arg-must-be-literal","errorCode":null,"errorMessage":"unit arg must be literal","messagePattern":"unit arg must be literal","errorType":"validation","errorClass":"DruidException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/expression/TimestampExtractExprMacro.java","lineNumber":149,"sourceCode":"        return ExpressionType.LONG;\n    }\n  }\n\n  private static ISOChronology computeChronology(final List<Expr> args, final Expr.ObjectBinding bindings)\n  {\n    String timeZoneVal = (String) args.get(2).eval(bindings).value();\n    return timeZoneVal != null\n           ? ISOChronology.getInstance(DateTimes.inferTzFromString(timeZoneVal))\n           : ISOChronology.getInstanceUTC();\n  }\n\n  @Override\n  public Expr apply(final List<Expr> args)\n  {\n    validationHelperCheckArgumentRange(args, 2, 3);\n\n    if (!args.get(1).isLiteral() || args.get(1).getLiteralValue() == null) {\n      throw validationFailed(\"unit arg must be literal\");\n    }\n\n    final Unit unit = Unit.valueOf(StringUtils.toUpperCase((String) args.get(1).getLiteralValue()));\n\n    if (args.size() > 2) {\n      if (args.get(2).isLiteral()) {\n        DateTimeZone timeZone = ExprUtils.toTimeZone(args.get(2));\n        ISOChronology chronology = ISOChronology.getInstance(timeZone);\n        return new TimestampExtractExpr(args, unit, chronology);\n      } else {\n        return new TimestampExtractDynamicExpr(args, unit);\n      }\n    }\n    return new TimestampExtractExpr(args, unit, ISOChronology.getInstanceUTC());\n  }\n\n  public class TimestampExtractExpr extends ExprMacroTable.BaseScalarMacroFunctionExpr\n  {","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/expression/TimestampExtractExprMacro.java#L131-L167","documentation":"timestamp_extract's macro requires the unit argument (second argument) to be a non-null literal, since the Unit enum is resolved at parse time via Unit.valueOf. A missing/NULL literal or a non-literal expression produces this validation failure.","triggerScenarios":"TIMESTAMP_TO_MILLIS-style TIMESTAMP_EXTRACT(ts, unit[, timezone]) calls where unit is a column, computed expression, NULL literal, or absent-but-null value.","commonSituations":"Unit names passed via query parameters and substituted as expressions; lowercase or misspelled unit strings wired through variables; generated expressions missing the literal.","solutions":["Pass the unit as a quoted string literal, e.g. TIMESTAMP_EXTRACT(ts, 'MINUTE')","Use a valid unit name that maps to TimestampExtractExprMacro.Unit (e.g. SECOND, MINUTE, HOUR, DAY, DOW, etc.)","Ensure the value is non-null and a literal in generated expression JSON"],"exampleFix":"// before\nTIMESTAMP_EXTRACT(__time, unit_col)\n// after\nTIMESTAMP_EXTRACT(__time, 'HOUR')","handlingStrategy":"validation","validationCode":"if (!(unitArg instanceof String)) {\n  throw new IllegalArgumentException(\"unit must be a literal string like 'MINUTE'\");\n}\nUnit.valueOf(unitArg.toUpperCase()); // verify it maps to a valid unit","typeGuard":"static boolean isUnitLiteral(Expr e) {\n  return e.isLiteral() && e.getLiteralValue() instanceof String;\n}","tryCatchPattern":"try {\n  return macro.apply(args);\n} catch (ExpressionValidationException e) {\n  log.error(\"timestamp_extract unit invalid: %s\", e.getMessage());\n  throw e;\n}","preventionTips":["Use quoted uppercase unit literals","Check the Unit enum for supported names","Reject non-literal unit arguments during query planning/linting"],"tags":["druid","timestamp","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-17T15:17:12.973Z"}