{"record":{"id":"e2ee49ad8ab299c6","repo":"apache/druid","slug":"projected-output-type-s-of-expression-s-does-not","errorCode":null,"errorMessage":"Projected output type %s of expression %s does not match provided type %s","messagePattern":"Projected output type (.+?) of expression (.+?) does not match provided type (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"processing/src/main/java/org/apache/druid/segment/virtual/ExpressionVirtualColumn.java","lineNumber":312,"sourceCode":"  @Override\n  public ColumnCapabilities capabilities(ColumnInspector inspector, String columnName)\n  {\n    if (isDirectAccess(inspector)) {\n      return inspector.getColumnCapabilities(expression.parsed.get().getBindingIfIdentifier());\n    }\n\n    final ColumnType outputType = expression.outputType;\n\n    final ExpressionPlan plan = ExpressionPlanner.plan(inspector, expression.parsed.get());\n    final ColumnCapabilities inferred = plan.inferColumnCapabilities(outputType);\n    // if we can infer the column capabilities from the expression plan, then use that\n    if (inferred != null) {\n      // explicit outputType is used as a hint, how did it compare to the planners inferred output type?\n      if (outputType != null && inferred.getType() != outputType.getType()) {\n        // if both sides are numeric, let it slide and log at debug level\n        // but mismatches involving strings and arrays might be worth knowing about so warn\n        if (!inferred.isNumeric() && !outputType.isNumeric()) {\n          log.warn(\n              \"Projected output type %s of expression %s does not match provided type %s\",\n              inferred.asTypeString(),\n              expression.expressionString,\n              outputType\n          );\n        } else {\n          log.debug(\n              \"Projected output type %s of expression %s does not match provided type %s\",\n              inferred.asTypeString(),\n              expression.expressionString,\n              outputType\n          );\n        }\n      }\n      return inferred;\n    }\n\n    // fallback to default capabilities","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/segment/virtual/ExpressionVirtualColumn.java#L294-L330","documentation":"ExpressionVirtualColumn.capabilities compares the output type inferred by evaluating the Druid expression against the explicitly configured outputType. If they differ and neither side is numeric, it warns that the projected type does not match the configured one; the inferred type is used. For numeric mismatches it only logs at debug level.","triggerScenarios":"An ExpressionVirtualColumn is configured with outputType (e.g. STRING) but the expression's inferred type differs (e.g. LONG or ARRAY), discovered when capabilities() is called during query planning.","commonSituations":"A user-specified outputType in the virtual column spec contradicts the expression, e.g. after editing the expression to return an array while keeping the old outputType, or a planner change altering inference.","solutions":["Align the column's outputType with the expression's inferred type","Update the expression so its result type matches the declared outputType","Treat the warning as informational if numeric (inference still wins for numerics)","Cast inside the expression (e.g. CAST(... AS VARCHAR)) to force the intended type"],"exampleFix":"// before\n// ExpressionVirtualColumn.forExpression(DruidExpression.of(ColumnType.STRING, expr), \"v0\", null) where expr returns LONG\n// after\n// ExpressionVirtualColumn.forExpression(DruidExpression.of(ColumnType.LONG, expr), \"v0\", null)\n// or cast: DruidExpression.of(ColumnType.STRING, \"CAST(expr AS VARCHAR)\")","handlingStrategy":"validation","validationCode":"// Before configuring, infer the expression type and compare with declared outputType\nColumnType inferred = Expressions.inferType(factory, parsedExpr);\nif (declaredOutputType != null && inferred != null && inferred.getType() != declaredOutputType.getType()) { /* fix spec */ }","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Keep outputType consistent with the expression's result type","Add explicit CAST in the expression when a specific output type is required","Re-check virtual column specs after editing expressions"],"tags":["expression","virtual-column","type-mismatch"],"backgroundTag":"type-mismatch","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"}