apache/druid · error · QueryUnsupportedException
Cannot apply virtual columns
Error message
Cannot apply virtual columns [%s] with naive apply.
What it means
The naive materialization path of LazilyDecoratedRowsAndColumns cannot apply virtual columns; it only handles skipping rows and selecting existing columns. When virtualColumns is set when the naive path is reached, Druid throws UOE. The fast cursor-based path must be used instead.
Solutions
- Ensure the base RowsAndColumns supports CursorFactory so the non-naive path is used
- Apply virtual columns before wrapping in LazilyDecoratedRowsAndColumns
- Remove virtual column decoration from this stage and compute derived columns elsewhere
Defensive patterns
Strategy: type-guard
Validate before calling
if (decoration.getVirtualColumns() != null && !(base instanceof CursorFactory)) {
throw new IllegalStateException("virtual columns require a cursor-capable base");
} Type guard
boolean supportsCursorPaths(RowsAndColumns rac) {
return rac.as(CursorFactory.class) != null;
} Try / catch
try {
rac = lazilyDecorated.materialize();
} catch (UnsupportedOperationException e) {
if (e.getMessage().contains("virtual columns") && e.getMessage().contains("naive")) {
rac = applyVirtualColumnsManually(base, vcs);
} else throw e;
} Prevention
- Only decorate cursor-capable RowsAndColumns with virtual columns
- Apply virtual columns upstream of the decorated wrapper
When it happens
Trigger: materialize() falls back to naiveMaterialize() (because the base is not a CursorFactory) while virtual columns are configured on the decoration.
Common situations: Wrapping a data object that doesn't support cursors while also decorating with virtual column expressions; internal pipeline wiring that combines naive materialization with VCs.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Cannot reorder[ ] scan data right now
- Map column doesn't support getRow()
- Map column doesn't support idLookup()
- Map column doesn't support lookupName()
- Aggregator[ ] cannot vectorize
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/8b348465b4e4a1fb.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/query/rowsandcols/LazilyDecoratedRowsAndColumns.java:350
rowsToSkip = new BitSet(numRows);
}
final ValueMatcher matcher = filter.makeMatcher(selectorFactory);
for (; rowId.get() < numRows; rowId.incrementAndGet()) {
final int theId = rowId.get();
if (rowsToSkip.get(theId)) {
continue;
}
if (!matcher.matches(false)) {
rowsToSkip.set(theId);
}
}
}
if (virtualColumns != null) {
throw new UOE("Cannot apply virtual columns [%s] with naive apply.", virtualColumns);
}
ArrayList<String> columnsToGenerate = new ArrayList<>();
if (viewableColumns != null) {
columnsToGenerate.addAll(viewableColumns);
} else {
columnsToGenerate.addAll(rac.getColumnNames());
// When/if we support virtual columns from here, we should auto-add them to the list here as well as they expand
// the implicit project when no projection is defined
}
// There is all sorts of sub-optimal things in this code, but we just ignore them for now as it is difficult to
// optimally build frames for incremental data processing. In order to build the frame object here, we must first
// materialize everything in memory so that we know how long things are, such that we can allocate a big byte[]
// so that the Frame.wrap() call can be given just a big byte[] to do its reading from.
//
// It would be a bit better if we could just build the per-column byte[] and somehow re-generate a Frame from
// that. But it's also possible that this impedence mis-match is a function of using column-oriented frames andView on GitHub (pinned to 9b90983fd2)