apache/iceberg · error
recursiveViewDetectedError(viewIdent, newCyclePath)
Error message
recursiveViewDetectedError(viewIdent, newCyclePath)
What it means
Iceberg's CheckViews detects cyclic view references during analysis: checkCyclicViewReference walks the analyzed plan tracking the chain of view identifiers, and if a view resolves back to itself, recursiveViewDetectedError is thrown with the full cycle path. This prevents infinite recursion and stack overflows when a view references itself directly or transitively.
Source
Thrown at spark/v4.2/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckViews.scala:118
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 QueryCompilationErrors.recursiveViewDetectedError(viewIdent, newCyclePath)
} else {
children.foreach { c =>
checkCyclicViewReference(viewIdent, c, newCyclePath)
}
}
}
}
View on GitHub (pinned to 86d9c8fc54)
Solutions
- Break the cycle: reference the underlying table instead of the view itself in the view definition
- Use a different name for the new view if the old definition was intended as the data source
- Restructure the view chain (v1 -> v2 -> v1) so references form a DAG
- Read the cycle path in the error to find which two views form the loop and fix one side
Example fix
-- before (recursive) CREATE OR REPLACE VIEW v AS SELECT * FROM v; -- after CREATE OR REPLACE VIEW v AS SELECT * FROM base_table;
Defensive patterns
Strategy: validation
Validate before calling
import org.apache.spark.sql.catalyst.analysis.CheckAnalysis
// inspect view definition for self-reference before replacing
val body = spark.sql("SELECT * FROM v").queryExecution.analyzed
val selfRef = body.collectLeaves().exists(_.name == "v")
require(!selfRef, "view definition references itself") Prevention
- When using CREATE OR REPLACE VIEW, never reference the same view name in the new definition
- Keep a dependency map of views; detect cycles before deploying SQL scripts
- Name replacement views distinctly (e.g. v2) when the old definition is intended as the source
When it happens
Trigger: `CREATE OR REPLACE VIEW v AS SELECT * FROM v` or a cycle like v1 -> v2 -> v1; the error fires in CheckViews when currentViewIdent equals the outer viewIdent being resolved, with newCyclePath showing the reference chain.
Common situations: CREATE OR REPLACE VIEW whose new definition still references the old view of the same name; accidentally reusing the view name in its replacement query; generated SQL templating a self-reference.
Related errors
- Recursive cycle in view detected: %s (cycle: %s)
- e (wrapped NoSuchNamespaceException)
- NoSuchViewException(ident)
- Creating a view is not supported by catalog: ${catalogName}
- Replacing a view is not supported by catalog: ${catalogName}
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/f8e1d3177e65b536.
Report an issue: GitHub.