diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/command/views.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/views.scala index 145287158a58c..eca48a6992433 100644 --- a/sql/core/src/main/scala/org/apache/spark/sql/execution/command/views.scala +++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/views.scala @@ -612,12 +612,17 @@ object ViewHelper extends SQLConfHelper with Logging { val uncache = getRawTempView(name.table).map { r => needsToUncache(r, aliasedPlan) }.getOrElse(false) + val storeAnalyzedPlanForView = conf.storeAnalyzedPlanForView || originalText.isEmpty if (replace && uncache) { logDebug(s"Try to uncache ${name.quotedString} before replacing.") - checkCyclicViewReference(analyzedPlan, Seq(name), name) + if (!storeAnalyzedPlanForView) { + // Skip cyclic check because when stored analyzed plan for view, the depended + // view is already converted to the underlying tables. So no cyclic views. + checkCyclicViewReference(analyzedPlan, Seq(name), name) + } CommandUtils.uncacheTableOrView(session, name.quotedString) } - if (!conf.storeAnalyzedPlanForView && originalText.nonEmpty) { + if (!storeAnalyzedPlanForView) { TemporaryViewRelation( prepareTemporaryView( name, diff --git a/sql/core/src/test/scala/org/apache/spark/sql/execution/SQLViewTestSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/execution/SQLViewTestSuite.scala index 94855f2c42143..1dc131e875828 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/execution/SQLViewTestSuite.scala +++ b/sql/core/src/test/scala/org/apache/spark/sql/execution/SQLViewTestSuite.scala @@ -19,7 +19,7 @@ package org.apache.spark.sql.execution import scala.collection.JavaConverters._ -import org.apache.spark.sql.{AnalysisException, QueryTest, Row} +import org.apache.spark.sql.{AnalysisException, DataFrame, QueryTest, Row} import org.apache.spark.sql.catalyst.{FunctionIdentifier, TableIdentifier} import org.apache.spark.sql.catalyst.catalog.CatalogFunction import org.apache.spark.sql.catalyst.expressions.Expression @@ -408,6 +408,9 @@ abstract class SQLViewTestSuite extends QueryTest with SQLTestUtils { } abstract class TempViewTestSuite extends SQLViewTestSuite { + + def createOrReplaceDatasetView(df: DataFrame, viewName: String): Unit + test("SPARK-37202: temp view should capture the function registered by catalog API") { val funcName = "tempFunc" withUserDefinedFunction(funcName -> true) { @@ -435,6 +438,28 @@ abstract class TempViewTestSuite extends SQLViewTestSuite { s"$viewName is a temp view. 'SHOW CREATE TABLE' expects a table or permanent view.")) } } + + test("back compatibility: skip cyclic reference check if view is stored as logical plan") { + val viewName = formattedViewName("v") + withSQLConf(STORE_ANALYZED_PLAN_FOR_VIEW.key -> "false") { + withView(viewName) { + createOrReplaceDatasetView(sql("SELECT 1"), "v") + createOrReplaceDatasetView(sql(s"SELECT * FROM $viewName"), "v") + checkViewOutput(viewName, Seq(Row(1))) + } + } + withSQLConf(STORE_ANALYZED_PLAN_FOR_VIEW.key -> "true") { + withView(viewName) { + createOrReplaceDatasetView(sql("SELECT 1"), "v") + createOrReplaceDatasetView(sql(s"SELECT * FROM $viewName"), "v") + checkViewOutput(viewName, Seq(Row(1))) + + createView("v", "SELECT 2", replace = true) + createView("v", s"SELECT * FROM $viewName", replace = true) + checkViewOutput(viewName, Seq(Row(2))) + } + } + } } class LocalTempViewTestSuite extends TempViewTestSuite with SharedSparkSession { @@ -443,6 +468,9 @@ class LocalTempViewTestSuite extends TempViewTestSuite with SharedSparkSession { override protected def tableIdentifier(viewName: String): TableIdentifier = { TableIdentifier(viewName) } + override def createOrReplaceDatasetView(df: DataFrame, viewName: String): Unit = { + df.createOrReplaceTempView(viewName) + } } class GlobalTempViewTestSuite extends TempViewTestSuite with SharedSparkSession { @@ -454,6 +482,9 @@ class GlobalTempViewTestSuite extends TempViewTestSuite with SharedSparkSession override protected def tableIdentifier(viewName: String): TableIdentifier = { TableIdentifier(viewName, Some(db)) } + override def createOrReplaceDatasetView(df: DataFrame, viewName: String): Unit = { + df.createOrReplaceGlobalTempView(viewName) + } } class OneTableCatalog extends InMemoryCatalog {