apache/druid · error · IllegalArgumentException
Self-referential column
Error message
Self-referential column[%s]
What it means
VirtualColumns.detectCycles validates the virtual-column dependency graph at construction time. When following requiredColumns(), a column that is already on the current path is encountered again, meaning a virtual column (transitively) depends on itself. Such a graph can never be evaluated, so construction fails with IAE.
Solutions
- Break the cycle: rewrite one expression so it no longer references itself (directly or transitively)
- Inline the dependent expression instead of referencing the alias
- Order definitions so each virtual column only references previously defined columns
Example fix
// before
VirtualColumns.of(
Expressions.as("a", "b + 1"),
Expressions.as("b", "a + 1")
)
// after
VirtualColumns.of(
Expressions.as("b_raw", "x + 1"),
Expressions.as("a", "b_raw + 1")
) Defensive patterns
Strategy: validation
Validate before calling
Set<String> path = new HashSet<>();
for (Map.Entry<String, List<String>> e : depGraph.entrySet()) {
Deque<String> stack = new ArrayDeque<>(List.of(e.getKey()));
// DFS: if any requiredColumns() name reappears on the current path, fail before building VirtualColumns
} Try / catch
try { VirtualColumns.of(defs); } catch (IAE e) { log.error("cyclic virtual column: {}", e.getMessage()); } Prevention
- Build dependency graph and check for cycles before constructing VirtualColumns
- Never let an expression reference its own alias
- Generate expressions programmatically with cycle detection in the generator
When it happens
Trigger: Building VirtualColumns where expression A references expression B and B (transitively) references A, or a virtual column lists itself in requiredColumns().
Common situations: Copy-pasting expression definitions where the new expression reuses its own alias; chaining query-time virtualized columns after a refactor; auto-generated specs from tooling that loops expressions.
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
- No such virtual column
- A-Not-B requires at least 1 sketch
- Access-Check-Result
- Action [ ] failed for worker [ ] with status ( )
- Actual Row count mismatch. Expected
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/27b35f7b086be158.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/segment/VirtualColumns.java:576
/**
* Detects cycles in the dependencies of a {@link VirtualColumn}.
*
* @param virtualColumn virtual column to check
* @param visited null on initial call. Internally, this method operates recursively, and uses this parameter
* to pass down the list of already-visited columns.
*/
private void detectCycles(VirtualColumn virtualColumn, @Nullable Set<String> visited)
{
// Copy "visited" to avoid modifying it
final Set<String> visitedCopy = visited == null
? Sets.newHashSet(virtualColumn.getOutputName())
: Sets.newHashSet(visited);
for (String columnName : virtualColumn.requiredColumns()) {
final VirtualColumn dependency = getVirtualColumn(columnName);
if (dependency != null) {
if (!visitedCopy.add(columnName)) {
throw new IAE("Self-referential column[%s]", columnName);
}
detectCycles(dependency, visitedCopy);
visitedCopy.remove(columnName);
}
}
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
VirtualColumns that = (VirtualColumns) o;View on GitHub (pinned to 9b90983fd2)