From 0e61c95bbabc04819e019e0656bf884fb479723b Mon Sep 17 00:00:00 2001 From: Dilip Biswal Date: Tue, 19 Mar 2019 16:15:40 -0700 Subject: [PATCH 1/5] [SPARK-27209] Split parsing of SELECT and INSERT into two top-level rules in the grammar file. --- .../spark/sql/catalyst/parser/SqlBase.g4 | 10 ++++- .../sql/catalyst/parser/AstBuilder.scala | 45 +++++++++++++------ .../sql/catalyst/parser/PlanParserSuite.scala | 25 +++++++++++ .../spark/sql/execution/SparkSqlParser.scala | 17 ------- .../sql/execution/SparkSqlParserSuite.scala | 22 --------- 5 files changed, 64 insertions(+), 55 deletions(-) diff --git a/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 b/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 index 1f7da190ce46..ce5652af4898 100644 --- a/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 +++ b/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 @@ -81,6 +81,7 @@ singleTableSchema statement : query #statementDefault + | insertStatement #insertStatementDefault | USE db=identifier #use | CREATE database (IF NOT EXISTS)? identifier (COMMENT comment=STRING)? locationSpec? @@ -358,9 +359,14 @@ resource : identifier STRING ; +insertStatement + : (ctes)? insertInto queryTerm queryOrganization #singleInsertQuery + | (ctes)? fromClause multiInsertQueryBody+ #multiInsertQuery + ; + queryNoWith - : insertInto? queryTerm queryOrganization #singleInsertQuery - | fromClause multiInsertQueryBody+ #multiInsertQuery + : queryTerm queryOrganization #noWithQuery + | fromClause querySpecification queryOrganization #queryWithFrom ; queryOrganization diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala index 52a5d2c66c5b..a20dfa2bdaa5 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala @@ -112,21 +112,37 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging val query = plan(ctx.queryNoWith) // Apply CTEs - query.optional(ctx.ctes) { - val ctes = ctx.ctes.namedQuery.asScala.map { nCtx => - val namedQuery = visitNamedQuery(nCtx) - (namedQuery.alias, namedQuery) - } - // Check for duplicate names. - checkDuplicateKeys(ctes, ctx) - With(query, ctes) + query.optionalMap(ctx.ctes)(withCTE) + } + + private def withCTE(ctx: CtesContext, plan: LogicalPlan): LogicalPlan = { + val ctes = ctx.namedQuery.asScala.map { nCtx => + val namedQuery = visitNamedQuery(nCtx) + (namedQuery.alias, namedQuery) } + // Check for duplicate names. + checkDuplicateKeys(ctes, ctx) + With(plan, ctes) } override def visitQueryToDesc(ctx: QueryToDescContext): LogicalPlan = withOrigin(ctx) { plan(ctx.queryTerm).optionalMap(ctx.queryOrganization)(withQueryResultClauses) } + override def visitQueryWithFrom(ctx: QueryWithFromContext): LogicalPlan = withOrigin(ctx) { + val from = visitFromClause(ctx.fromClause) + validate(ctx.querySpecification.fromClause == null, + "Script transformation queries cannot have a FROM clause in their" + + " individual SELECT statements", ctx) + withQuerySpecification(ctx.querySpecification, from). + // Add organization statements. + optionalMap(ctx.queryOrganization)(withQueryResultClauses) + } + + override def visitNoWithQuery(ctx: NoWithQueryContext): LogicalPlan = withOrigin(ctx) { + plan(ctx.queryTerm).optionalMap(ctx.queryOrganization)(withQueryResultClauses) + } + /** * Create a named logical plan. * @@ -170,10 +186,12 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging } // If there are multiple INSERTS just UNION them together into one query. - inserts match { + val insertPlan = inserts match { case Seq(query) => query case queries => Union(queries) } + // Apply CTEs + insertPlan.optionalMap(ctx.ctes)(withCTE) } /** @@ -181,11 +199,10 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging */ override def visitSingleInsertQuery( ctx: SingleInsertQueryContext): LogicalPlan = withOrigin(ctx) { - plan(ctx.queryTerm). - // Add organization statements. - optionalMap(ctx.queryOrganization)(withQueryResultClauses). - // Add insert. - optionalMap(ctx.insertInto())(withInsertInto) + val insertPlan = withInsertInto(ctx.insertInto(), + plan(ctx.queryTerm).optionalMap(ctx.queryOrganization)(withQueryResultClauses)) + // Apply CTEs + insertPlan.optionalMap(ctx.ctes)(withCTE) } /** diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala index d628150d3ecf..5309fcda25f8 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala @@ -754,4 +754,29 @@ class PlanParserSuite extends AnalysisTest { assertEqual(query2, Distinct(a.union(b)).except(c.intersect(d, isAll = true), isAll = true)) } } + + test("create/alter view as insert into table") { + intercept[ParseException](parsePlan("CREATE VIEW testView AS INSERT INTO jt VALUES(1, 1)")) + val sql1 = + """ + |CREATE VIEW testView AS FROM jt + |INSERT INTO tbl1 SELECT * WHERE jt.id < 5 " + + |INSERT INTO tbl2 SELECT * WHERE jt.id > 4 + """.stripMargin + intercept[ParseException](parsePlan(sql1)) + intercept[ParseException](parsePlan("ALTER VIEW testView AS INSERT INTO jt VALUES(1, 1)")) + // Multiinsert query + val sql2 = + """ + |ALTER VIEW testView AS FROM jt + |INSERT INTO tbl1 SELECT * WHERE jt.id < 5 + |INSERT INTO tbl2 SELECT * WHERE jt.id > 4 + """.stripMargin + intercept[ParseException](parsePlan(sql2)) + } + + test("Invalid insert constructs in the query") { + intercept[ParseException](parsePlan("SELECT * FROM (INSERT INTO BAR VALUES (2))")) + intercept[ParseException](parsePlan("SELECT * FROM S WHERE C1 IN (INSERT INTO T VALUES (2))")) + } } diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala index 735c0a5758d9..8c7b2cb50c0b 100644 --- a/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala +++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala @@ -1257,15 +1257,6 @@ class SparkSqlAstBuilder(conf: SQLConf) extends AstBuilder(conf) { if (ctx.identifierList != null) { operationNotAllowed("CREATE VIEW ... PARTITIONED ON", ctx) } else { - // CREATE VIEW ... AS INSERT INTO is not allowed. - ctx.query.queryNoWith match { - case s: SingleInsertQueryContext if s.insertInto != null => - operationNotAllowed("CREATE VIEW ... AS INSERT INTO", ctx) - case _: MultiInsertQueryContext => - operationNotAllowed("CREATE VIEW ... AS FROM ... [INSERT INTO ...]+", ctx) - case _ => // OK - } - val userSpecifiedColumns = Option(ctx.identifierCommentList).toSeq.flatMap { icl => icl.identifierComment.asScala.map { ic => ic.identifier.getText -> Option(ic.STRING).map(string) @@ -1302,14 +1293,6 @@ class SparkSqlAstBuilder(conf: SQLConf) extends AstBuilder(conf) { * }}} */ override def visitAlterViewQuery(ctx: AlterViewQueryContext): LogicalPlan = withOrigin(ctx) { - // ALTER VIEW ... AS INSERT INTO is not allowed. - ctx.query.queryNoWith match { - case s: SingleInsertQueryContext if s.insertInto != null => - operationNotAllowed("ALTER VIEW ... AS INSERT INTO", ctx) - case _: MultiInsertQueryContext => - operationNotAllowed("ALTER VIEW ... AS FROM ... [INSERT INTO ...]+", ctx) - case _ => // OK - } AlterViewAsCommand( name = visitTableIdentifier(ctx.tableIdentifier), originalText = source(ctx.query), diff --git a/sql/core/src/test/scala/org/apache/spark/sql/execution/SparkSqlParserSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/execution/SparkSqlParserSuite.scala index be3d0794d403..fd60779b49fd 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/execution/SparkSqlParserSuite.scala +++ b/sql/core/src/test/scala/org/apache/spark/sql/execution/SparkSqlParserSuite.scala @@ -215,17 +215,6 @@ class SparkSqlParserSuite extends AnalysisTest { "no viable alternative at input") } - test("create view as insert into table") { - // Single insert query - intercept("CREATE VIEW testView AS INSERT INTO jt VALUES(1, 1)", - "Operation not allowed: CREATE VIEW ... AS INSERT INTO") - - // Multi insert query - intercept("CREATE VIEW testView AS FROM jt INSERT INTO tbl1 SELECT * WHERE jt.id < 5 " + - "INSERT INTO tbl2 SELECT * WHERE jt.id > 4", - "Operation not allowed: CREATE VIEW ... AS FROM ... [INSERT INTO ...]+") - } - test("SPARK-17328 Fix NPE with EXPLAIN DESCRIBE TABLE") { assertEqual("describe t", DescribeTableCommand(TableIdentifier("t"), Map.empty, isExtended = false)) @@ -369,17 +358,6 @@ class SparkSqlParserSuite extends AnalysisTest { Project(UnresolvedAlias(concat) :: Nil, UnresolvedRelation(TableIdentifier("t")))) } - test("SPARK-25046 Fix Alter View ... As Insert Into Table") { - // Single insert query - intercept("ALTER VIEW testView AS INSERT INTO jt VALUES(1, 1)", - "Operation not allowed: ALTER VIEW ... AS INSERT INTO") - - // Multi insert query - intercept("ALTER VIEW testView AS FROM jt INSERT INTO tbl1 SELECT * WHERE jt.id < 5 " + - "INSERT INTO tbl2 SELECT * WHERE jt.id > 4", - "Operation not allowed: ALTER VIEW ... AS FROM ... [INSERT INTO ...]+") - } - test("database and schema tokens are interchangeable") { assertEqual("CREATE DATABASE foo", parser.parsePlan("CREATE SCHEMA foo")) assertEqual("DROP DATABASE foo", parser.parsePlan("DROP SCHEMA foo")) From fa07a739eb3e91d2a4892c5dc61e2a093f0befa8 Mon Sep 17 00:00:00 2001 From: Dilip Biswal Date: Wed, 20 Mar 2019 06:02:24 -0700 Subject: [PATCH 2/5] Code review --- .../org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala index 5309fcda25f8..5d30d46e4ee4 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala @@ -760,7 +760,7 @@ class PlanParserSuite extends AnalysisTest { val sql1 = """ |CREATE VIEW testView AS FROM jt - |INSERT INTO tbl1 SELECT * WHERE jt.id < 5 " + + |INSERT INTO tbl1 SELECT * WHERE jt.id < 5 |INSERT INTO tbl2 SELECT * WHERE jt.id > 4 """.stripMargin intercept[ParseException](parsePlan(sql1)) From 18afacdf09d3c7dbd10ff7da31e601c0835315c3 Mon Sep 17 00:00:00 2001 From: Dilip Biswal Date: Wed, 20 Mar 2019 14:55:43 -0700 Subject: [PATCH 3/5] Code review --- .../sql/catalyst/parser/PlanParserSuite.scala | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala index 5d30d46e4ee4..a423fde0bac8 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala @@ -756,27 +756,45 @@ class PlanParserSuite extends AnalysisTest { } test("create/alter view as insert into table") { - intercept[ParseException](parsePlan("CREATE VIEW testView AS INSERT INTO jt VALUES(1, 1)")) - val sql1 = - """ - |CREATE VIEW testView AS FROM jt - |INSERT INTO tbl1 SELECT * WHERE jt.id < 5 - |INSERT INTO tbl2 SELECT * WHERE jt.id > 4 - """.stripMargin - intercept[ParseException](parsePlan(sql1)) - intercept[ParseException](parsePlan("ALTER VIEW testView AS INSERT INTO jt VALUES(1, 1)")) - // Multiinsert query - val sql2 = - """ - |ALTER VIEW testView AS FROM jt - |INSERT INTO tbl1 SELECT * WHERE jt.id < 5 - |INSERT INTO tbl2 SELECT * WHERE jt.id > 4 - """.stripMargin - intercept[ParseException](parsePlan(sql2)) + val m1 = intercept[ParseException] { + parsePlan("CREATE VIEW testView AS INSERT INTO jt VALUES(1, 1)") + }.getMessage + assert(m1.contains("mismatched input 'INSERT' expecting")) + // Multi insert query + val m2 = intercept[ParseException] { + parsePlan( + """ + |CREATE VIEW testView AS FROM jt + |INSERT INTO tbl1 SELECT * WHERE jt.id < 5 + |INSERT INTO tbl2 SELECT * WHERE jt.id > 4 + """.stripMargin) + }.getMessage + assert(m2.contains("mismatched input 'INSERT' expecting")) + val m3 = intercept[ParseException] { + parsePlan("ALTER VIEW testView AS INSERT INTO jt VALUES(1, 1)") + }.getMessage + assert(m3.contains("mismatched input 'INSERT' expecting")) + // Multi insert query + val m4 = intercept[ParseException] { + parsePlan( + """ + |ALTER VIEW testView AS FROM jt + |INSERT INTO tbl1 SELECT * WHERE jt.id < 5 + |INSERT INTO tbl2 SELECT * WHERE jt.id > 4 + """.stripMargin + ) + }.getMessage + assert(m4.contains("mismatched input 'INSERT' expecting")) } test("Invalid insert constructs in the query") { - intercept[ParseException](parsePlan("SELECT * FROM (INSERT INTO BAR VALUES (2))")) - intercept[ParseException](parsePlan("SELECT * FROM S WHERE C1 IN (INSERT INTO T VALUES (2))")) + val m1 = intercept[ParseException] { + parsePlan("SELECT * FROM (INSERT INTO BAR VALUES (2))") + }.getMessage + assert(m1.contains("mismatched input 'FROM' expecting")) + val m2 = intercept[ParseException] { + parsePlan("SELECT * FROM S WHERE C1 IN (INSERT INTO T VALUES (2))") + }.getMessage + assert(m2.contains("mismatched input 'FROM' expecting")) } } From f7aaebd5e45e1136d74f71bbe99617629c786efd Mon Sep 17 00:00:00 2001 From: Dilip Biswal Date: Fri, 22 Mar 2019 17:40:47 -0700 Subject: [PATCH 4/5] separate rule for multi inserts --- .../spark/sql/catalyst/parser/SqlBase.g4 | 15 +++++-- .../sql/catalyst/parser/AstBuilder.scala | 44 ++++++++++++++----- .../sql/catalyst/parser/PlanParserSuite.scala | 2 +- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 b/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 index ce5652af4898..78dc60c02758 100644 --- a/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 +++ b/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 @@ -82,6 +82,7 @@ singleTableSchema statement : query #statementDefault | insertStatement #insertStatementDefault + | multiSelectStatement #multiSelectStatementDefault | USE db=identifier #use | CREATE database (IF NOT EXISTS)? identifier (COMMENT comment=STRING)? locationSpec? @@ -366,7 +367,7 @@ insertStatement queryNoWith : queryTerm queryOrganization #noWithQuery - | fromClause querySpecification queryOrganization #queryWithFrom + | fromClause selectStatement #queryWithFrom ; queryOrganization @@ -379,9 +380,15 @@ queryOrganization ; multiInsertQueryBody - : insertInto? - querySpecification - queryOrganization + : insertInto selectStatement + ; + +multiSelectStatement + : (ctes)? fromClause selectStatement+ #multiSelect + ; + +selectStatement + : querySpecification queryOrganization ; queryTerm diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala index a20dfa2bdaa5..d057b65fa928 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala @@ -131,12 +131,11 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging override def visitQueryWithFrom(ctx: QueryWithFromContext): LogicalPlan = withOrigin(ctx) { val from = visitFromClause(ctx.fromClause) - validate(ctx.querySpecification.fromClause == null, + validate(ctx.selectStatement.querySpecification.fromClause == null, "Script transformation queries cannot have a FROM clause in their" + " individual SELECT statements", ctx) - withQuerySpecification(ctx.querySpecification, from). - // Add organization statements. - optionalMap(ctx.queryOrganization)(withQueryResultClauses) + withQuerySpecification(ctx.selectStatement.querySpecification, from). + optionalMap(ctx.selectStatement.queryOrganization)(withQueryResultClauses) } override def visitNoWithQuery(ctx: NoWithQueryContext): LogicalPlan = withOrigin(ctx) { @@ -172,17 +171,16 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging val from = visitFromClause(ctx.fromClause) // Build the insert clauses. - val inserts = ctx.multiInsertQueryBody.asScala.map { + val inserts = ctx.multiInsertQueryBody().asScala.map { body => - validate(body.querySpecification.fromClause == null, + validate(body.selectStatement.querySpecification.fromClause == null, "Multi-Insert queries cannot have a FROM clause in their individual SELECT statements", body) - withQuerySpecification(body.querySpecification, from). - // Add organization statements. - optionalMap(body.queryOrganization)(withQueryResultClauses). - // Add insert. - optionalMap(body.insertInto())(withInsertInto) + withInsertInto(body.insertInto, + withQuerySpecification(body.selectStatement.querySpecification, from). + // Add organization statements. + optionalMap(body.selectStatement.queryOrganization)(withQueryResultClauses)) } // If there are multiple INSERTS just UNION them together into one query. @@ -194,6 +192,30 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging insertPlan.optionalMap(ctx.ctes)(withCTE) } + override def visitMultiSelect(ctx: MultiSelectContext): LogicalPlan = withOrigin(ctx) { + val from = visitFromClause(ctx.fromClause) + + // Build the insert clauses. + val selects = ctx.selectStatement.asScala.map { + body => + validate(body.querySpecification.fromClause == null, + "Multi-select queries cannot have a FROM clause in their individual SELECT statements", + body) + + withQuerySpecification(body.querySpecification, from). + // Add organization statements. + optionalMap(body.queryOrganization)(withQueryResultClauses) + } + + // If there are multiple INSERTS just UNION them together into one query. + val selectUnionPlan = selects match { + case Seq(query) => query + case queries => Union(queries) + } + // Apply CTEs + selectUnionPlan.optionalMap(ctx.ctes)(withCTE) + } + /** * Create a logical plan for a regular (single-insert) query. */ diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala index a423fde0bac8..e6ab15f91146 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala @@ -132,7 +132,7 @@ class PlanParserSuite extends AnalysisTest { table("a").select(star()).union(table("a").where('s < 10).select(star()))) intercept( "from a select * select * from x where a.s < 10", - "Multi-Insert queries cannot have a FROM clause in their individual SELECT statements") + "Multi-select queries cannot have a FROM clause in their individual SELECT statements") assertEqual( "from a insert into tbl1 select * insert into tbl2 select * where s < 10", table("a").select(star()).insertInto("tbl1").union( From ea05263827d377b54408ae76de7fdffa4569c1be Mon Sep 17 00:00:00 2001 From: Dilip Biswal Date: Fri, 22 Mar 2019 18:29:36 -0700 Subject: [PATCH 5/5] Code review --- .../org/apache/spark/sql/catalyst/parser/AstBuilder.scala | 4 ++-- .../apache/spark/sql/catalyst/parser/PlanParserSuite.scala | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala index d057b65fa928..7164ad26029c 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala @@ -132,8 +132,8 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging override def visitQueryWithFrom(ctx: QueryWithFromContext): LogicalPlan = withOrigin(ctx) { val from = visitFromClause(ctx.fromClause) validate(ctx.selectStatement.querySpecification.fromClause == null, - "Script transformation queries cannot have a FROM clause in their" + - " individual SELECT statements", ctx) + "Individual select statement can not have FROM cause as its already specified in the" + + " outer query block", ctx) withQuerySpecification(ctx.selectStatement.querySpecification, from). optionalMap(ctx.selectStatement.queryOrganization)(withQueryResultClauses) } diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala index e6ab15f91146..9208d9358016 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/PlanParserSuite.scala @@ -133,6 +133,10 @@ class PlanParserSuite extends AnalysisTest { intercept( "from a select * select * from x where a.s < 10", "Multi-select queries cannot have a FROM clause in their individual SELECT statements") + intercept( + "from a select * from b", + "Individual select statement can not have FROM cause as its already specified in " + + "the outer query block") assertEqual( "from a insert into tbl1 select * insert into tbl2 select * where s < 10", table("a").select(star()).insertInto("tbl1").union(