apache/iceberg · error · AnalysisException
Recursive cycle in view detected
Error message
Recursive cycle in view detected: %s (cycle: %s)
What it means
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.
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
Example fix
// before CREATE OR REPLACE VIEW catalog.db.v AS SELECT * FROM catalog.db.v // after CREATE OR REPLACE VIEW catalog.db.v AS SELECT * FROM catalog.db.base_table
Defensive patterns
Strategy: validation
Validate before calling
// maintain a map viewName -> referencedViews and detect cycles before creating
def hasCycle(deps: Map[String, Set[String]], start: String): Boolean = {
@annotation.tailrec def walk(frontier: Set[String], seen: Set[String]): Boolean =
if (frontier.contains(start)) true
else if (frontier.subsetOf(seen) || frontier.isEmpty) false
else walk(frontier.flatMap(deps.getOrElse(_, Set.empty)) -- seen, seen ++ frontier)
walk(deps.getOrElse(start, Set.empty), Set.empty)
} Try / catch
try { spark.sql(viewDdl) } catch { case e: AnalysisException if e.getMessage.contains("Recursive cycle in view") => // redefine one view to break the loop } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- CREATE_VIEW_COLUMN_ARITY_MISMATCH.TOO_MANY_DATA_COLUMNS
- ALTER TABLE contains multiple distribution clauses
- ALTER TABLE contains multiple distribution clauses
- ALTER TABLE contains multiple ordering clauses
- ALTER TABLE contains multiple ordering clauses
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/b727faed22bcd13c.
Report an issue: GitHub.
Appendix: source
Thrown at spark/v3.5/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckViews.scala:124
plan.children.foreach(child => checkCyclicViewReference(viewIdent, child, cyclePath))
}
plan.expressions.flatMap(_.flatMap {
case e: SubqueryExpression =>
checkCyclicViewReference(viewIdent, e.plan, cyclePath)
None
case _ => None
})
}
private def checkIfRecursiveView(
viewIdent: Seq[String],
currentViewIdent: Seq[String],
cyclePath: Seq[Seq[String]],
children: Seq[LogicalPlan]): Unit = {
val newCyclePath = cyclePath :+ currentViewIdent
if (currentViewIdent == viewIdent) {
throw new AnalysisException(
String.format(
"Recursive cycle in view detected: %s (cycle: %s)",
viewIdent.asIdentifier,
newCyclePath.map(p => p.mkString(".")).mkString(" -> ")))
} else {
children.foreach { c =>
checkCyclicViewReference(viewIdent, c, newCyclePath)
}
}
}
}
View on GitHub (pinned to 86d9c8fc54)