{"record":{"id":"b727faed22bcd13c","repo":"apache/iceberg","slug":"recursive-cycle-in-view-detected-s-cycle-s","errorCode":null,"errorMessage":"Recursive cycle in view detected: %s (cycle: %s)","messagePattern":"Recursive cycle in view detected: (.+?) \\(cycle: (.+?)\\)","errorType":"exception","errorClass":"AnalysisException","httpStatus":null,"severity":"error","filePath":"spark/v3.5/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckViews.scala","lineNumber":124,"sourceCode":"        plan.children.foreach(child => checkCyclicViewReference(viewIdent, child, cyclePath))\n    }\n\n    plan.expressions.flatMap(_.flatMap {\n      case e: SubqueryExpression =>\n        checkCyclicViewReference(viewIdent, e.plan, cyclePath)\n        None\n      case _ => None\n    })\n  }\n\n  private def checkIfRecursiveView(\n      viewIdent: Seq[String],\n      currentViewIdent: Seq[String],\n      cyclePath: Seq[Seq[String]],\n      children: Seq[LogicalPlan]): Unit = {\n    val newCyclePath = cyclePath :+ currentViewIdent\n    if (currentViewIdent == viewIdent) {\n      throw new AnalysisException(\n        String.format(\n          \"Recursive cycle in view detected: %s (cycle: %s)\",\n          viewIdent.asIdentifier,\n          newCyclePath.map(p => p.mkString(\".\")).mkString(\" -> \")))\n    } else {\n      children.foreach { c =>\n        checkCyclicViewReference(viewIdent, c, newCyclePath)\n      }\n    }\n  }\n}\n","sourceCodeStart":106,"sourceCodeEnd":136,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v3.5/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckViews.scala#L106-L136","documentation":"Thrown by Iceberg's CheckViews rule when view resolution detects a recursive reference cycle: a view (directly or through other views) references itself, which would cause infinite resolution. The message names the view and the full cycle path.","triggerScenarios":"CREATE OR REPLACE VIEW v AS SELECT * FROM v (directly or via a chain v -> w -> v) evaluated against an Iceberg catalog; the CheckViews rule walks view children and matches identifier equality against the current cycle path.","commonSituations":"CREATE OR REPLACE VIEW on an existing view whose definition already references views that now resolve back to it; renaming views so a dependency chain closes into a loop; generating view DDL programmatically with circular dependencies.","solutions":["Break the cycle by redefining one view in the chain so it no longer references (directly or transitively) itself","Base the view on the underlying table instead of a view that resolves back to it","Draw the view dependency graph (the cycle is printed in the error) and reorder creation so dependencies form a DAG"],"exampleFix":"// before\nCREATE OR REPLACE VIEW catalog.db.v AS SELECT * FROM catalog.db.v\n// after\nCREATE OR REPLACE VIEW catalog.db.v AS SELECT * FROM catalog.db.base_table","handlingStrategy":"validation","validationCode":"// maintain a map viewName -> referencedViews and detect cycles before creating\ndef hasCycle(deps: Map[String, Set[String]], start: String): Boolean = {\n  @annotation.tailrec def walk(frontier: Set[String], seen: Set[String]): Boolean =\n    if (frontier.contains(start)) true\n    else if (frontier.subsetOf(seen) || frontier.isEmpty) false\n    else walk(frontier.flatMap(deps.getOrElse(_, Set.empty)) -- seen, seen ++ frontier)\n  walk(deps.getOrElse(start, Set.empty), Set.empty)\n}","typeGuard":null,"tryCatchPattern":"try { spark.sql(viewDdl) } catch { case e: AnalysisException if e.getMessage.contains(\"Recursive cycle in view\") => // redefine one view to break the loop }","preventionTips":["Keep a dependency registry of view definitions and run cycle detection before DDL","Never CREATE OR REPLACE a view with a definition that references views created from it","When renaming views, update all dependent definitions in the same change"],"tags":["spark","sql","view-ddl","recursive-dependency"],"backgroundTag":"internal-invariant-violation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}