diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala index 389bbb828da6f..c699942ab55ca 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala @@ -680,6 +680,12 @@ trait CheckAnalysis extends PredicateHelper with LookupCatalog { |Invalid expressions: [${invalidExprSqls.mkString(", ")}]""".stripMargin ) + case c: Command => + c.innerChildren.foreach { + case l: LogicalPlan => checkAnalysis(l) + case _ => + } + case _ => // Analysis successful! } } diff --git a/sql/core/src/main/scala/org/apache/spark/sql/Dataset.scala b/sql/core/src/main/scala/org/apache/spark/sql/Dataset.scala index fd02d0b131587..edbf584fea2df 100644 --- a/sql/core/src/main/scala/org/apache/spark/sql/Dataset.scala +++ b/sql/core/src/main/scala/org/apache/spark/sql/Dataset.scala @@ -3370,7 +3370,7 @@ class Dataset[T] private[sql]( comment = None, properties = Map.empty, originalText = None, - child = logicalPlan, + analyzedPlan = logicalPlan, allowExisting = false, replace = replace, viewType = viewType) 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 b302b268b3afc..fc376ed0fc2d1 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 @@ -48,8 +48,8 @@ import org.apache.spark.sql.util.SchemaUtils * @param properties the properties of this view. * @param originalText the original SQL text of this view, can be None if this view is created via * Dataset API. - * @param child the logical plan that represents the view; this is used to generate the logical - * plan for temporary view and the view schema. + * @param analyzedPlan the logical plan that represents the view; this is used to generate the + * logical plan for temporary view and the view schema. * @param allowExisting if true, and if the view already exists, noop; if false, and if the view * already exists, throws analysis exception. * @param replace if true, and if the view already exists, updates it; if false, and if the view @@ -62,7 +62,7 @@ case class CreateViewCommand( comment: Option[String], properties: Map[String, String], originalText: Option[String], - child: LogicalPlan, + analyzedPlan: LogicalPlan, allowExisting: Boolean, replace: Boolean, viewType: ViewType) @@ -70,7 +70,7 @@ case class CreateViewCommand( import ViewHelper._ - override def innerChildren: Seq[QueryPlan[_]] = Seq(child) + override def innerChildren: Seq[QueryPlan[_]] = Seq(analyzedPlan) if (viewType == PersistedView) { require(originalText.isDefined, "'originalText' must be provided to create permanent view") @@ -96,11 +96,6 @@ case class CreateViewCommand( } override def run(sparkSession: SparkSession): Seq[Row] = { - // If the plan cannot be analyzed, throw an exception and don't proceed. - val qe = sparkSession.sessionState.executePlan(child) - qe.assertAnalyzed() - val analyzedPlan = qe.analyzed - if (userSpecifiedColumns.nonEmpty && userSpecifiedColumns.length != analyzedPlan.output.length) { throw new AnalysisException(s"The number of columns produced by the SELECT clause " + diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/CacheTableExec.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/CacheTableExec.scala index 56e008d1d95f8..0c0a0227f99b2 100644 --- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/CacheTableExec.scala +++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/CacheTableExec.scala @@ -94,6 +94,13 @@ case class CacheTableAsSelectExec( override lazy val relationName: String = tempViewName override lazy val planToCache: LogicalPlan = { + // CacheTableAsSelectExec.query is not resolved yet (e.g., not a child of CacheTableAsSelect) + // in order to skip optimizing it; note that we need to pass an analyzed plan to + // CreateViewCommand for the cache to work correctly. Thus, the query is analyzed below. + val qe = sparkSession.sessionState.executePlan(query) + qe.assertAnalyzed() + val analyzedPlan = qe.analyzed + Dataset.ofRows(sparkSession, CreateViewCommand( name = TableIdentifier(tempViewName), @@ -101,7 +108,7 @@ case class CacheTableAsSelectExec( comment = None, properties = Map.empty, originalText = Some(originalText), - child = query, + analyzedPlan = analyzedPlan, allowExisting = false, replace = false, viewType = LocalTempView