@@ -63,9 +63,12 @@ case class CreateViewCommand(
6363 }
6464
6565 override def run (sqlContext : SQLContext ): Seq [Row ] = {
66- val analzyedPlan = sqlContext.executePlan(child).analyzed
66+ // If the plan cannot be analyzed, throw an exception and don't proceed.
67+ val qe = sqlContext.executePlan(child)
68+ qe.assertAnalyzed()
69+ val analyzedPlan = qe.analyzed
6770
68- require(tableDesc.schema == Nil || tableDesc.schema.length == analzyedPlan .output.length)
71+ require(tableDesc.schema == Nil || tableDesc.schema.length == analyzedPlan .output.length)
6972 val sessionState = sqlContext.sessionState
7073
7174 if (sessionState.catalog.tableExists(tableIdentifier)) {
@@ -74,7 +77,7 @@ case class CreateViewCommand(
7477 // already exists.
7578 } else if (replace) {
7679 // Handles `CREATE OR REPLACE VIEW v0 AS SELECT ...`
77- sessionState.catalog.alterTable(prepareTable(sqlContext, analzyedPlan ))
80+ sessionState.catalog.alterTable(prepareTable(sqlContext, analyzedPlan ))
7881 } else {
7982 // Handles `CREATE VIEW v0 AS SELECT ...`. Throws exception when the target view already
8083 // exists.
@@ -85,68 +88,74 @@ case class CreateViewCommand(
8588 } else {
8689 // Create the view if it doesn't exist.
8790 sessionState.catalog.createTable(
88- prepareTable(sqlContext, analzyedPlan ), ignoreIfExists = false )
91+ prepareTable(sqlContext, analyzedPlan ), ignoreIfExists = false )
8992 }
9093
9194 Seq .empty[Row ]
9295 }
9396
94- private def prepareTable (sqlContext : SQLContext , analzyedPlan : LogicalPlan ): CatalogTable = {
95- val expandedText = if (sqlContext.conf.canonicalView) {
96- try rebuildViewQueryString(sqlContext, analzyedPlan) catch {
97- case NonFatal (e) => wrapViewTextWithSelect(analzyedPlan)
97+ /**
98+ * Returns a [[CatalogTable ]] that can be used to save in the catalog. This comment canonicalize
99+ * SQL based on the analyzed plan, and also creates the proper schema for the view.
100+ */
101+ private def prepareTable (sqlContext : SQLContext , analyzedPlan : LogicalPlan ): CatalogTable = {
102+ val viewSQL : String =
103+ if (sqlContext.conf.canonicalView) {
104+ val logicalPlan =
105+ if (tableDesc.schema.isEmpty) {
106+ analyzedPlan
107+ } else {
108+ val projectList = analyzedPlan.output.zip(tableDesc.schema).map {
109+ case (attr, col) => Alias (attr, col.name)()
110+ }
111+ sqlContext.executePlan(Project (projectList, analyzedPlan)).analyzed
112+ }
113+ new SQLBuilder (logicalPlan).toSQL
114+ } else {
115+ // When user specified column names for view, we should create a project to do the renaming.
116+ // When no column name specified, we still need to create a project to declare the columns
117+ // we need, to make us more robust to top level `*`s.
118+ val viewOutput = {
119+ val columnNames = analyzedPlan.output.map(f => quote(f.name))
120+ if (tableDesc.schema.isEmpty) {
121+ columnNames.mkString(" , " )
122+ } else {
123+ columnNames.zip(tableDesc.schema.map(f => quote(f.name))).map {
124+ case (name, alias) => s " $name AS $alias"
125+ }.mkString(" , " )
126+ }
127+ }
128+
129+ val viewText = tableDesc.viewText.get
130+ val viewName = quote(tableDesc.identifier.table)
131+ s " SELECT $viewOutput FROM ( $viewText) $viewName"
98132 }
99- } else {
100- wrapViewTextWithSelect(analzyedPlan)
133+
134+ // Validate the view SQL - make sure we can parse it and analyze it.
135+ // If we cannot analyze the generated query, there is probably a bug in SQL generation.
136+ try {
137+ sqlContext.sql(viewSQL).queryExecution.assertAnalyzed()
138+ } catch {
139+ case NonFatal (e) =>
140+ throw new RuntimeException (
141+ " Failed to analyze the canonicalized SQL. It is possible there is a bug in Spark." , e)
101142 }
102143
103- val viewSchema = {
144+ val viewSchema : Seq [ CatalogColumn ] = {
104145 if (tableDesc.schema.isEmpty) {
105- analzyedPlan .output.map { a =>
146+ analyzedPlan .output.map { a =>
106147 CatalogColumn (a.name, a.dataType.simpleString)
107148 }
108149 } else {
109- analzyedPlan .output.zip(tableDesc.schema).map { case (a, col) =>
150+ analyzedPlan .output.zip(tableDesc.schema).map { case (a, col) =>
110151 CatalogColumn (col.name, a.dataType.simpleString, nullable = true , col.comment)
111152 }
112153 }
113154 }
114155
115- tableDesc.copy(schema = viewSchema, viewText = Some (expandedText))
116- }
117-
118- private def wrapViewTextWithSelect (analzyedPlan : LogicalPlan ): String = {
119- // When user specified column names for view, we should create a project to do the renaming.
120- // When no column name specified, we still need to create a project to declare the columns
121- // we need, to make us more robust to top level `*`s.
122- val viewOutput = {
123- val columnNames = analzyedPlan.output.map(f => quote(f.name))
124- if (tableDesc.schema.isEmpty) {
125- columnNames.mkString(" , " )
126- } else {
127- columnNames.zip(tableDesc.schema.map(f => quote(f.name))).map {
128- case (name, alias) => s " $name AS $alias"
129- }.mkString(" , " )
130- }
131- }
132-
133- val viewText = tableDesc.viewText.get
134- val viewName = quote(tableDesc.identifier.table)
135- s " SELECT $viewOutput FROM ( $viewText) $viewName"
136- }
137-
138- private def rebuildViewQueryString (sqlContext : SQLContext , analzyedPlan : LogicalPlan ): String = {
139- val logicalPlan = if (tableDesc.schema.isEmpty) {
140- analzyedPlan
141- } else {
142- val projectList = analzyedPlan.output.zip(tableDesc.schema).map {
143- case (attr, col) => Alias (attr, col.name)()
144- }
145- sqlContext.executePlan(Project (projectList, analzyedPlan)).analyzed
146- }
147- new SQLBuilder (logicalPlan).toSQL
156+ tableDesc.copy(schema = viewSchema, viewText = Some (viewSQL))
148157 }
149158
150- // escape backtick with double-backtick in column name and wrap it with backtick.
159+ /** Escape backtick with double-backtick in column name and wrap it with backtick. */
151160 private def quote (name : String ) = s " ` ${name.replaceAll(" `" , " ``" )}` "
152161}
0 commit comments