apache/iceberg · error · IcebergAnalysisException
Recursive cycle in view detected: %s (cycle: %s)
Error message
Recursive cycle in view detected: %s (cycle: %s)
What it means
CheckViews performs recursive view-reference detection when creating or altering Iceberg views. If, while walking the query plan, the current view identifier equals an identifier already on the resolution path, a cyclic view definition exists and this IcebergAnalysisException is thrown, showing the view and the full cycle path (view -> view -> ...).
Source
Thrown at spark/v4.1/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 IcebergAnalysisException(
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)
Solutions
- Break the cycle: base the new definition on the underlying table instead of the view itself
- Create the new view under a temporary name, then swap/rename after dropping the old one
- Inspect the printed cycle path and remove the self-referencing dependency in the view DDL
Example fix
// before CREATE OR REPLACE VIEW v AS SELECT * FROM v WHERE id > 0 // after CREATE OR REPLACE VIEW v AS SELECT * FROM t WHERE id > 0
Defensive patterns
Strategy: validation
Validate before calling
// verify the view name doesn't appear among relations referenced by the query
val refs = spark.sql(query).queryExecution.analyzed.collect { case r: org.apache.spark.sql.catalyst.analysis.ResolvedTable => r.identifier.toString }
require(!refs.contains(viewName.toString), s"Cycle: $viewName references itself") Try / catch
try { spark.sql(ddl) } catch { case e: IcebergAnalysisException if e.getMessage.startsWith("Recursive cycle in view") => log.error(e.getMessage); /* break the cycle and retry */ } Prevention
- Base new view definitions on tables, not on the view being replaced
- Read the cycle path in the message to locate the loop
- Use temp views during migration, then rename
When it happens
Trigger: `CREATE OR REPLACE VIEW v AS SELECT * FROM v` or mutual cycles v1 -> v2 -> v1, where the new view definition (directly or transitively) references itself.
Common situations: Re-creating an existing view from a query that still reads the old view name; scripted view refresh that regenerates views from queries captured earlier.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- ALTER VIEW <viewName> AS is not supported. Use CREATE OR REP
- CREATE_VIEW_COLUMN_ARITY_MISMATCH.NOT_ENOUGH_DATA_COLUMNS
- CREATE_VIEW_COLUMN_ARITY_MISMATCH.TOO_MANY_DATA_COLUMNS
- Invalid value for ${SparkSQLProperties.VIEW_SCHEMA_BINDING_M
- recursiveViewDetectedError(viewIdent, newCyclePath)
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/fe5828fa7c5d4068.
Report an issue: GitHub.