apache/druid · error · DruidException
unit arg must be literal
Error message
unit arg must be literal
What it means
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.
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
Example fix
// before TIMESTAMP_EXTRACT(__time, unit_col) // after TIMESTAMP_EXTRACT(__time, 'HOUR')
Defensive patterns
Strategy: validation
Validate before calling
if (!(unitArg instanceof String)) {
throw new IllegalArgumentException("unit must be a literal string like 'MINUTE'");
}
Unit.valueOf(unitArg.toUpperCase()); // verify it maps to a valid unit Type guard
static boolean isUnitLiteral(Expr e) {
return e.isLiteral() && e.getLiteralValue() instanceof String;
} Try / catch
try {
return macro.apply(args);
} catch (ExpressionValidationException e) {
log.error("timestamp_extract unit invalid: %s", e.getMessage());
throw e;
} Prevention
- Use quoted uppercase unit literals
- Check the Unit enum for supported names
- Reject non-literal unit arguments during query planning/linting
When it happens
Trigger: TIMESTAMP_TO_MILLIS-style TIMESTAMP_EXTRACT(ts, unit[, timezone]) calls where unit is a column, computed expression, NULL literal, or absent-but-null value.
Common situations: Unit names passed via query parameters and substituted as expressions; lowercase or misspelled unit strings wired through variables; generated expressions missing the literal.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- index must be a numeric literal
- pattern must be a string literal
- pattern must be a STRING literal
- pattern must be a string literal
- replacement must be a string literal
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/2ea412e312c1d070.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/query/expression/TimestampExtractExprMacro.java:149
return ExpressionType.LONG;
}
}
private static ISOChronology computeChronology(final List<Expr> args, final Expr.ObjectBinding bindings)
{
String timeZoneVal = (String) args.get(2).eval(bindings).value();
return timeZoneVal != null
? ISOChronology.getInstance(DateTimes.inferTzFromString(timeZoneVal))
: ISOChronology.getInstanceUTC();
}
@Override
public Expr apply(final List<Expr> args)
{
validationHelperCheckArgumentRange(args, 2, 3);
if (!args.get(1).isLiteral() || args.get(1).getLiteralValue() == null) {
throw validationFailed("unit arg must be literal");
}
final Unit unit = Unit.valueOf(StringUtils.toUpperCase((String) args.get(1).getLiteralValue()));
if (args.size() > 2) {
if (args.get(2).isLiteral()) {
DateTimeZone timeZone = ExprUtils.toTimeZone(args.get(2));
ISOChronology chronology = ISOChronology.getInstance(timeZone);
return new TimestampExtractExpr(args, unit, chronology);
} else {
return new TimestampExtractDynamicExpr(args, unit);
}
}
return new TimestampExtractExpr(args, unit, ISOChronology.getInstanceUTC());
}
public class TimestampExtractExpr extends ExprMacroTable.BaseScalarMacroFunctionExpr
{View on GitHub (pinned to 9b90983fd2)