{"record":{"id":"f8e1d3177e65b536","repo":"apache/iceberg","slug":"recursiveviewdetectederror-viewident-newcyclepath","errorCode":null,"errorMessage":"recursiveViewDetectedError(viewIdent, newCyclePath)","messagePattern":"recursiveViewDetectedError\\(viewIdent, newCyclePath\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"spark/v4.2/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckViews.scala","lineNumber":118,"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 QueryCompilationErrors.recursiveViewDetectedError(viewIdent, newCyclePath)\n    } else {\n      children.foreach { c =>\n        checkCyclicViewReference(viewIdent, c, newCyclePath)\n      }\n    }\n  }\n}\n","sourceCodeStart":100,"sourceCodeEnd":126,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.2/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckViews.scala#L100-L126","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","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"],"exampleFix":"-- before (recursive)\nCREATE OR REPLACE VIEW v AS SELECT * FROM v;\n-- after\nCREATE OR REPLACE VIEW v AS SELECT * FROM base_table;","handlingStrategy":"validation","validationCode":"import org.apache.spark.sql.catalyst.analysis.CheckAnalysis\n// inspect view definition for self-reference before replacing\nval body = spark.sql(\"SELECT * FROM v\").queryExecution.analyzed\nval selfRef = body.collectLeaves().exists(_.name == \"v\")\nrequire(!selfRef, \"view definition references itself\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["spark","views","recursion"],"backgroundTag":"recursive-view-reference","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}