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

  1. Break the cycle: rewrite one expression so it no longer references itself (directly or transitively)
  2. Inline the dependent expression instead of referencing the alias
  3. 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

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


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)