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

  1. Pass the unit as a quoted string literal, e.g. TIMESTAMP_EXTRACT(ts, 'MINUTE')
  2. Use a valid unit name that maps to TimestampExtractExprMacro.Unit (e.g. SECOND, MINUTE, HOUR, DAY, DOW, etc.)
  3. 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

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


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)