{"record":{"id":"27b35f7b086be158","repo":"apache/druid","slug":"self-referential-column-s","errorCode":null,"errorMessage":"Self-referential column[%s]","messagePattern":"Self-referential column\\[(.+?)\\]","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/segment/VirtualColumns.java","lineNumber":576,"sourceCode":"  /**\n   * Detects cycles in the dependencies of a {@link VirtualColumn}.\n   *\n   * @param virtualColumn virtual column to check\n   * @param visited       null on initial call. Internally, this method operates recursively, and uses this parameter\n   *                      to pass down the list of already-visited columns.\n   */\n  private void detectCycles(VirtualColumn virtualColumn, @Nullable Set<String> visited)\n  {\n    // Copy \"visited\" to avoid modifying it\n    final Set<String> visitedCopy = visited == null\n                                    ? Sets.newHashSet(virtualColumn.getOutputName())\n                                    : Sets.newHashSet(visited);\n\n    for (String columnName : virtualColumn.requiredColumns()) {\n      final VirtualColumn dependency = getVirtualColumn(columnName);\n      if (dependency != null) {\n        if (!visitedCopy.add(columnName)) {\n          throw new IAE(\"Self-referential column[%s]\", columnName);\n        }\n        detectCycles(dependency, visitedCopy);\n        visitedCopy.remove(columnName);\n      }\n    }\n  }\n\n  @Override\n  public boolean equals(Object o)\n  {\n    if (this == o) {\n      return true;\n    }\n    if (o == null || getClass() != o.getClass()) {\n      return false;\n    }\n\n    VirtualColumns that = (VirtualColumns) o;","sourceCodeStart":558,"sourceCodeEnd":594,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/segment/VirtualColumns.java#L558-L594","documentation":"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.","triggerScenarios":"Building VirtualColumns where expression A references expression B and B (transitively) references A, or a virtual column lists itself in requiredColumns().","commonSituations":"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.","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"],"exampleFix":"// before\nVirtualColumns.of(\n  Expressions.as(\"a\", \"b + 1\"),\n  Expressions.as(\"b\", \"a + 1\")\n)\n// after\nVirtualColumns.of(\n  Expressions.as(\"b_raw\", \"x + 1\"),\n  Expressions.as(\"a\", \"b_raw + 1\")\n)","handlingStrategy":"validation","validationCode":"Set<String> path = new HashSet<>();\nfor (Map.Entry<String, List<String>> e : depGraph.entrySet()) {\n  Deque<String> stack = new ArrayDeque<>(List.of(e.getKey()));\n  // DFS: if any requiredColumns() name reappears on the current path, fail before building VirtualColumns\n}","typeGuard":null,"tryCatchPattern":"try { VirtualColumns.of(defs); } catch (IAE e) { log.error(\"cyclic virtual column: {}\", e.getMessage()); }","preventionTips":["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"],"tags":["druid","virtual-columns","cyclic-dependency"],"backgroundTag":"invalid-argument-value","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"}