| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.spark.sql.catalyst.parser |
| |
| import java.util.Locale |
| |
| import org.apache.spark.sql.AnalysisException |
| import org.apache.spark.sql.catalyst.analysis.{AnalysisTest, GlobalTempView, LocalTempView, PersistedView, UnresolvedAttribute, UnresolvedFunc, UnresolvedNamespace, UnresolvedRelation, UnresolvedStar, UnresolvedTable, UnresolvedTableOrView} |
| import org.apache.spark.sql.catalyst.catalog.{ArchiveResource, BucketSpec, FileResource, FunctionResource, FunctionResourceType, JarResource} |
| import org.apache.spark.sql.catalyst.expressions.{EqualTo, Literal} |
| import org.apache.spark.sql.catalyst.plans.logical._ |
| import org.apache.spark.sql.connector.catalog.TableChange.ColumnPosition.{after, first} |
| import org.apache.spark.sql.connector.expressions.{ApplyTransform, BucketTransform, DaysTransform, FieldReference, HoursTransform, IdentityTransform, LiteralValue, MonthsTransform, Transform, YearsTransform} |
| import org.apache.spark.sql.internal.SQLConf |
| import org.apache.spark.sql.types.{IntegerType, LongType, StringType, StructType, TimestampType} |
| import org.apache.spark.unsafe.types.UTF8String |
| |
| class DDLParserSuite extends AnalysisTest { |
| import CatalystSqlParser._ |
| |
| private def assertUnsupported(sql: String, containsThesePhrases: Seq[String] = Seq()): Unit = { |
| val e = intercept[ParseException] { |
| parsePlan(sql) |
| } |
| assert(e.getMessage.toLowerCase(Locale.ROOT).contains("operation not allowed")) |
| containsThesePhrases.foreach { p => |
| assert(e.getMessage.toLowerCase(Locale.ROOT).contains(p.toLowerCase(Locale.ROOT))) |
| } |
| } |
| |
| private def intercept(sqlCommand: String, messages: String*): Unit = |
| interceptParseException(parsePlan)(sqlCommand, messages: _*) |
| |
| private def parseCompare(sql: String, expected: LogicalPlan): Unit = { |
| comparePlans(parsePlan(sql), expected, checkAnalysis = false) |
| } |
| |
| test("create/replace table using - schema") { |
| val createSql = "CREATE TABLE my_tab(a INT COMMENT 'test', b STRING NOT NULL) USING parquet" |
| val replaceSql = "REPLACE TABLE my_tab(a INT COMMENT 'test', b STRING NOT NULL) USING parquet" |
| val expectedTableSpec = TableSpec( |
| Seq("my_tab"), |
| Some(new StructType() |
| .add("a", IntegerType, nullable = true, "test") |
| .add("b", StringType, nullable = false)), |
| Seq.empty[Transform], |
| None, |
| Map.empty[String, String], |
| Some("parquet"), |
| Map.empty[String, String], |
| None, |
| None) |
| |
| Seq(createSql, replaceSql).foreach { sql => |
| testCreateOrReplaceDdl(sql, expectedTableSpec, expectedIfNotExists = false) |
| } |
| |
| intercept("CREATE TABLE my_tab(a: INT COMMENT 'test', b: STRING) USING parquet", |
| "no viable alternative at input") |
| } |
| |
| test("create/replace table - with IF NOT EXISTS") { |
| val sql = "CREATE TABLE IF NOT EXISTS my_tab(a INT, b STRING) USING parquet" |
| testCreateOrReplaceDdl( |
| sql, |
| TableSpec( |
| Seq("my_tab"), |
| Some(new StructType().add("a", IntegerType).add("b", StringType)), |
| Seq.empty[Transform], |
| None, |
| Map.empty[String, String], |
| Some("parquet"), |
| Map.empty[String, String], |
| None, |
| None), |
| expectedIfNotExists = true) |
| } |
| |
| test("create/replace table - with partitioned by") { |
| val createSql = "CREATE TABLE my_tab(a INT comment 'test', b STRING) " + |
| "USING parquet PARTITIONED BY (a)" |
| val replaceSql = "REPLACE TABLE my_tab(a INT comment 'test', b STRING) " + |
| "USING parquet PARTITIONED BY (a)" |
| val expectedTableSpec = TableSpec( |
| Seq("my_tab"), |
| Some(new StructType() |
| .add("a", IntegerType, nullable = true, "test") |
| .add("b", StringType)), |
| Seq(IdentityTransform(FieldReference("a"))), |
| None, |
| Map.empty[String, String], |
| Some("parquet"), |
| Map.empty[String, String], |
| None, |
| None) |
| Seq(createSql, replaceSql).foreach { sql => |
| testCreateOrReplaceDdl(sql, expectedTableSpec, expectedIfNotExists = false) |
| } |
| } |
| |
| test("create/replace table - partitioned by transforms") { |
| val createSql = |
| """ |
| |CREATE TABLE my_tab (a INT, b STRING, ts TIMESTAMP) USING parquet |
| |PARTITIONED BY ( |
| | a, |
| | bucket(16, b), |
| | years(ts), |
| | months(ts), |
| | days(ts), |
| | hours(ts), |
| | foo(a, "bar", 34)) |
| """.stripMargin |
| |
| val replaceSql = |
| """ |
| |REPLACE TABLE my_tab (a INT, b STRING, ts TIMESTAMP) USING parquet |
| |PARTITIONED BY ( |
| | a, |
| | bucket(16, b), |
| | years(ts), |
| | months(ts), |
| | days(ts), |
| | hours(ts), |
| | foo(a, "bar", 34)) |
| """.stripMargin |
| val expectedTableSpec = TableSpec( |
| Seq("my_tab"), |
| Some(new StructType() |
| .add("a", IntegerType) |
| .add("b", StringType) |
| .add("ts", TimestampType)), |
| Seq( |
| IdentityTransform(FieldReference("a")), |
| BucketTransform(LiteralValue(16, IntegerType), Seq(FieldReference("b"))), |
| YearsTransform(FieldReference("ts")), |
| MonthsTransform(FieldReference("ts")), |
| DaysTransform(FieldReference("ts")), |
| HoursTransform(FieldReference("ts")), |
| ApplyTransform("foo", Seq( |
| FieldReference("a"), |
| LiteralValue(UTF8String.fromString("bar"), StringType), |
| LiteralValue(34, IntegerType)))), |
| None, |
| Map.empty[String, String], |
| Some("parquet"), |
| Map.empty[String, String], |
| None, |
| None) |
| Seq(createSql, replaceSql).foreach { sql => |
| testCreateOrReplaceDdl(sql, expectedTableSpec, expectedIfNotExists = false) |
| } |
| } |
| |
| test("create/replace table - with bucket") { |
| val createSql = "CREATE TABLE my_tab(a INT, b STRING) USING parquet " + |
| "CLUSTERED BY (a) SORTED BY (b) INTO 5 BUCKETS" |
| |
| val replaceSql = "REPLACE TABLE my_tab(a INT, b STRING) USING parquet " + |
| "CLUSTERED BY (a) SORTED BY (b) INTO 5 BUCKETS" |
| |
| val expectedTableSpec = TableSpec( |
| Seq("my_tab"), |
| Some(new StructType().add("a", IntegerType).add("b", StringType)), |
| Seq.empty[Transform], |
| Some(BucketSpec(5, Seq("a"), Seq("b"))), |
| Map.empty[String, String], |
| Some("parquet"), |
| Map.empty[String, String], |
| None, |
| None) |
| Seq(createSql, replaceSql).foreach { sql => |
| testCreateOrReplaceDdl(sql, expectedTableSpec, expectedIfNotExists = false) |
| } |
| } |
| |
| test("create/replace table - with comment") { |
| val createSql = "CREATE TABLE my_tab(a INT, b STRING) USING parquet COMMENT 'abc'" |
| val replaceSql = "REPLACE TABLE my_tab(a INT, b STRING) USING parquet COMMENT 'abc'" |
| val expectedTableSpec = TableSpec( |
| Seq("my_tab"), |
| Some(new StructType().add("a", IntegerType).add("b", StringType)), |
| Seq.empty[Transform], |
| None, |
| Map.empty[String, String], |
| Some("parquet"), |
| Map.empty[String, String], |
| None, |
| Some("abc")) |
| Seq(createSql, replaceSql).foreach{ sql => |
| testCreateOrReplaceDdl(sql, expectedTableSpec, expectedIfNotExists = false) |
| } |
| } |
| |
| test("create/replace table - with table properties") { |
| val createSql = "CREATE TABLE my_tab(a INT, b STRING) USING parquet" + |
| " TBLPROPERTIES('test' = 'test')" |
| val replaceSql = "REPLACE TABLE my_tab(a INT, b STRING) USING parquet" + |
| " TBLPROPERTIES('test' = 'test')" |
| val expectedTableSpec = TableSpec( |
| Seq("my_tab"), |
| Some(new StructType().add("a", IntegerType).add("b", StringType)), |
| Seq.empty[Transform], |
| None, |
| Map("test" -> "test"), |
| Some("parquet"), |
| Map.empty[String, String], |
| None, |
| None) |
| Seq(createSql, replaceSql).foreach { sql => |
| testCreateOrReplaceDdl(sql, expectedTableSpec, expectedIfNotExists = false) |
| } |
| } |
| |
| test("create/replace table - with location") { |
| val createSql = "CREATE TABLE my_tab(a INT, b STRING) USING parquet LOCATION '/tmp/file'" |
| val replaceSql = "REPLACE TABLE my_tab(a INT, b STRING) USING parquet LOCATION '/tmp/file'" |
| val expectedTableSpec = TableSpec( |
| Seq("my_tab"), |
| Some(new StructType().add("a", IntegerType).add("b", StringType)), |
| Seq.empty[Transform], |
| None, |
| Map.empty[String, String], |
| Some("parquet"), |
| Map.empty[String, String], |
| Some("/tmp/file"), |
| None) |
| Seq(createSql, replaceSql).foreach { sql => |
| testCreateOrReplaceDdl(sql, expectedTableSpec, expectedIfNotExists = false) |
| } |
| } |
| |
| test("create/replace table - byte length literal table name") { |
| val createSql = "CREATE TABLE 1m.2g(a INT) USING parquet" |
| val replaceSql = "REPLACE TABLE 1m.2g(a INT) USING parquet" |
| val expectedTableSpec = TableSpec( |
| Seq("1m", "2g"), |
| Some(new StructType().add("a", IntegerType)), |
| Seq.empty[Transform], |
| None, |
| Map.empty[String, String], |
| Some("parquet"), |
| Map.empty[String, String], |
| None, |
| None) |
| Seq(createSql, replaceSql).foreach { sql => |
| testCreateOrReplaceDdl(sql, expectedTableSpec, expectedIfNotExists = false) |
| } |
| } |
| |
| test("Duplicate clauses - create/replace table") { |
| def createTableHeader(duplicateClause: String): String = { |
| s"CREATE TABLE my_tab(a INT, b STRING) USING parquet $duplicateClause $duplicateClause" |
| } |
| |
| def replaceTableHeader(duplicateClause: String): String = { |
| s"CREATE TABLE my_tab(a INT, b STRING) USING parquet $duplicateClause $duplicateClause" |
| } |
| |
| intercept(createTableHeader("TBLPROPERTIES('test' = 'test2')"), |
| "Found duplicate clauses: TBLPROPERTIES") |
| intercept(createTableHeader("LOCATION '/tmp/file'"), |
| "Found duplicate clauses: LOCATION") |
| intercept(createTableHeader("COMMENT 'a table'"), |
| "Found duplicate clauses: COMMENT") |
| intercept(createTableHeader("CLUSTERED BY(b) INTO 256 BUCKETS"), |
| "Found duplicate clauses: CLUSTERED BY") |
| intercept(createTableHeader("PARTITIONED BY (b)"), |
| "Found duplicate clauses: PARTITIONED BY") |
| |
| intercept(replaceTableHeader("TBLPROPERTIES('test' = 'test2')"), |
| "Found duplicate clauses: TBLPROPERTIES") |
| intercept(replaceTableHeader("LOCATION '/tmp/file'"), |
| "Found duplicate clauses: LOCATION") |
| intercept(replaceTableHeader("COMMENT 'a table'"), |
| "Found duplicate clauses: COMMENT") |
| intercept(replaceTableHeader("CLUSTERED BY(b) INTO 256 BUCKETS"), |
| "Found duplicate clauses: CLUSTERED BY") |
| intercept(replaceTableHeader("PARTITIONED BY (b)"), |
| "Found duplicate clauses: PARTITIONED BY") |
| } |
| |
| test("support for other types in OPTIONS") { |
| val createSql = |
| """ |
| |CREATE TABLE table_name USING json |
| |OPTIONS (a 1, b 0.1, c TRUE) |
| """.stripMargin |
| val replaceSql = |
| """ |
| |REPLACE TABLE table_name USING json |
| |OPTIONS (a 1, b 0.1, c TRUE) |
| """.stripMargin |
| Seq(createSql, replaceSql).foreach { sql => |
| testCreateOrReplaceDdl( |
| sql, |
| TableSpec( |
| Seq("table_name"), |
| Some(new StructType), |
| Seq.empty[Transform], |
| Option.empty[BucketSpec], |
| Map.empty[String, String], |
| Some("json"), |
| Map("a" -> "1", "b" -> "0.1", "c" -> "true"), |
| None, |
| None), |
| expectedIfNotExists = false) |
| } |
| } |
| |
| test("Test CTAS against native tables") { |
| val s1 = |
| """ |
| |CREATE TABLE IF NOT EXISTS mydb.page_view |
| |USING parquet |
| |COMMENT 'This is the staging page view table' |
| |LOCATION '/user/external/page_view' |
| |TBLPROPERTIES ('p1'='v1', 'p2'='v2') |
| |AS SELECT * FROM src |
| """.stripMargin |
| |
| val s2 = |
| """ |
| |CREATE TABLE IF NOT EXISTS mydb.page_view |
| |USING parquet |
| |LOCATION '/user/external/page_view' |
| |COMMENT 'This is the staging page view table' |
| |TBLPROPERTIES ('p1'='v1', 'p2'='v2') |
| |AS SELECT * FROM src |
| """.stripMargin |
| |
| val s3 = |
| """ |
| |CREATE TABLE IF NOT EXISTS mydb.page_view |
| |USING parquet |
| |COMMENT 'This is the staging page view table' |
| |LOCATION '/user/external/page_view' |
| |TBLPROPERTIES ('p1'='v1', 'p2'='v2') |
| |AS SELECT * FROM src |
| """.stripMargin |
| |
| val s4 = |
| """ |
| |REPLACE TABLE mydb.page_view |
| |USING parquet |
| |COMMENT 'This is the staging page view table' |
| |LOCATION '/user/external/page_view' |
| |TBLPROPERTIES ('p1'='v1', 'p2'='v2') |
| |AS SELECT * FROM src |
| """.stripMargin |
| |
| val expectedTableSpec = TableSpec( |
| Seq("mydb", "page_view"), |
| None, |
| Seq.empty[Transform], |
| None, |
| Map("p1" -> "v1", "p2" -> "v2"), |
| Some("parquet"), |
| Map.empty[String, String], |
| Some("/user/external/page_view"), |
| Some("This is the staging page view table")) |
| Seq(s1, s2, s3, s4).foreach { sql => |
| testCreateOrReplaceDdl(sql, expectedTableSpec, expectedIfNotExists = true) |
| } |
| } |
| |
| test("drop table") { |
| parseCompare("DROP TABLE testcat.ns1.ns2.tbl", |
| DropTable( |
| UnresolvedTableOrView(Seq("testcat", "ns1", "ns2", "tbl")), |
| ifExists = false, |
| purge = false)) |
| parseCompare(s"DROP TABLE db.tab", |
| DropTable( |
| UnresolvedTableOrView(Seq("db", "tab")), ifExists = false, purge = false)) |
| parseCompare(s"DROP TABLE IF EXISTS db.tab", |
| DropTable( |
| UnresolvedTableOrView(Seq("db", "tab")), ifExists = true, purge = false)) |
| parseCompare(s"DROP TABLE tab", |
| DropTable( |
| UnresolvedTableOrView(Seq("tab")), ifExists = false, purge = false)) |
| parseCompare(s"DROP TABLE IF EXISTS tab", |
| DropTable( |
| UnresolvedTableOrView(Seq("tab")), ifExists = true, purge = false)) |
| parseCompare(s"DROP TABLE tab PURGE", |
| DropTable( |
| UnresolvedTableOrView(Seq("tab")), ifExists = false, purge = true)) |
| parseCompare(s"DROP TABLE IF EXISTS tab PURGE", |
| DropTable( |
| UnresolvedTableOrView(Seq("tab")), ifExists = true, purge = true)) |
| } |
| |
| test("drop view") { |
| parseCompare(s"DROP VIEW testcat.db.view", |
| DropViewStatement(Seq("testcat", "db", "view"), ifExists = false)) |
| parseCompare(s"DROP VIEW db.view", DropViewStatement(Seq("db", "view"), ifExists = false)) |
| parseCompare(s"DROP VIEW IF EXISTS db.view", |
| DropViewStatement(Seq("db", "view"), ifExists = true)) |
| parseCompare(s"DROP VIEW view", DropViewStatement(Seq("view"), ifExists = false)) |
| parseCompare(s"DROP VIEW IF EXISTS view", DropViewStatement(Seq("view"), ifExists = true)) |
| } |
| |
| private def testCreateOrReplaceDdl( |
| sqlStatement: String, |
| tableSpec: TableSpec, |
| expectedIfNotExists: Boolean): Unit = { |
| val parsedPlan = parsePlan(sqlStatement) |
| val newTableToken = sqlStatement.split(" ")(0).trim.toUpperCase(Locale.ROOT) |
| parsedPlan match { |
| case create: CreateTableStatement if newTableToken == "CREATE" => |
| assert(create.ifNotExists == expectedIfNotExists) |
| case ctas: CreateTableAsSelectStatement if newTableToken == "CREATE" => |
| assert(ctas.ifNotExists == expectedIfNotExists) |
| case replace: ReplaceTableStatement if newTableToken == "REPLACE" => |
| case replace: ReplaceTableAsSelectStatement if newTableToken == "REPLACE" => |
| case other => |
| fail("First token in statement does not match the expected parsed plan; CREATE TABLE" + |
| " should create a CreateTableStatement, and REPLACE TABLE should create a" + |
| s" ReplaceTableStatement. Statement: $sqlStatement, plan type:" + |
| s" ${parsedPlan.getClass.getName}.") |
| } |
| assert(TableSpec(parsedPlan) === tableSpec) |
| } |
| |
| // ALTER VIEW view_name SET TBLPROPERTIES ('comment' = new_comment); |
| // ALTER VIEW view_name UNSET TBLPROPERTIES [IF EXISTS] ('comment', 'key'); |
| test("alter view: alter view properties") { |
| val sql1_view = "ALTER VIEW table_name SET TBLPROPERTIES ('test' = 'test', " + |
| "'comment' = 'new_comment')" |
| val sql2_view = "ALTER VIEW table_name UNSET TBLPROPERTIES ('comment', 'test')" |
| val sql3_view = "ALTER VIEW table_name UNSET TBLPROPERTIES IF EXISTS ('comment', 'test')" |
| |
| comparePlans(parsePlan(sql1_view), |
| AlterViewSetPropertiesStatement( |
| Seq("table_name"), Map("test" -> "test", "comment" -> "new_comment"))) |
| comparePlans(parsePlan(sql2_view), |
| AlterViewUnsetPropertiesStatement( |
| Seq("table_name"), Seq("comment", "test"), ifExists = false)) |
| comparePlans(parsePlan(sql3_view), |
| AlterViewUnsetPropertiesStatement( |
| Seq("table_name"), Seq("comment", "test"), ifExists = true)) |
| } |
| |
| // ALTER TABLE table_name SET TBLPROPERTIES ('comment' = new_comment); |
| // ALTER TABLE table_name UNSET TBLPROPERTIES [IF EXISTS] ('comment', 'key'); |
| test("alter table: alter table properties") { |
| val sql1_table = "ALTER TABLE table_name SET TBLPROPERTIES ('test' = 'test', " + |
| "'comment' = 'new_comment')" |
| val sql2_table = "ALTER TABLE table_name UNSET TBLPROPERTIES ('comment', 'test')" |
| val sql3_table = "ALTER TABLE table_name UNSET TBLPROPERTIES IF EXISTS ('comment', 'test')" |
| |
| comparePlans( |
| parsePlan(sql1_table), |
| AlterTableSetPropertiesStatement( |
| Seq("table_name"), Map("test" -> "test", "comment" -> "new_comment"))) |
| comparePlans( |
| parsePlan(sql2_table), |
| AlterTableUnsetPropertiesStatement( |
| Seq("table_name"), Seq("comment", "test"), ifExists = false)) |
| comparePlans( |
| parsePlan(sql3_table), |
| AlterTableUnsetPropertiesStatement( |
| Seq("table_name"), Seq("comment", "test"), ifExists = true)) |
| } |
| |
| test("alter table: add column") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ADD COLUMN x int"), |
| AlterTableAddColumnsStatement(Seq("table_name"), Seq( |
| QualifiedColType(Seq("x"), IntegerType, true, None, None) |
| ))) |
| } |
| |
| test("alter table: add multiple columns") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ADD COLUMNS x int, y string"), |
| AlterTableAddColumnsStatement(Seq("table_name"), Seq( |
| QualifiedColType(Seq("x"), IntegerType, true, None, None), |
| QualifiedColType(Seq("y"), StringType, true, None, None) |
| ))) |
| } |
| |
| test("alter table: add column with COLUMNS") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ADD COLUMNS x int"), |
| AlterTableAddColumnsStatement(Seq("table_name"), Seq( |
| QualifiedColType(Seq("x"), IntegerType, true, None, None) |
| ))) |
| } |
| |
| test("alter table: add column with COLUMNS (...)") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ADD COLUMNS (x int)"), |
| AlterTableAddColumnsStatement(Seq("table_name"), Seq( |
| QualifiedColType(Seq("x"), IntegerType, true, None, None) |
| ))) |
| } |
| |
| test("alter table: add column with COLUMNS (...) and COMMENT") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ADD COLUMNS (x int COMMENT 'doc')"), |
| AlterTableAddColumnsStatement(Seq("table_name"), Seq( |
| QualifiedColType(Seq("x"), IntegerType, true, Some("doc"), None) |
| ))) |
| } |
| |
| test("alter table: add non-nullable column") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ADD COLUMN x int NOT NULL"), |
| AlterTableAddColumnsStatement(Seq("table_name"), Seq( |
| QualifiedColType(Seq("x"), IntegerType, false, None, None) |
| ))) |
| } |
| |
| test("alter table: add column with COMMENT") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ADD COLUMN x int COMMENT 'doc'"), |
| AlterTableAddColumnsStatement(Seq("table_name"), Seq( |
| QualifiedColType(Seq("x"), IntegerType, true, Some("doc"), None) |
| ))) |
| } |
| |
| test("alter table: add column with position") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ADD COLUMN x int FIRST"), |
| AlterTableAddColumnsStatement(Seq("table_name"), Seq( |
| QualifiedColType(Seq("x"), IntegerType, true, None, Some(first())) |
| ))) |
| |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ADD COLUMN x int AFTER y"), |
| AlterTableAddColumnsStatement(Seq("table_name"), Seq( |
| QualifiedColType(Seq("x"), IntegerType, true, None, Some(after("y"))) |
| ))) |
| } |
| |
| test("alter table: add column with nested column name") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ADD COLUMN x.y.z int COMMENT 'doc'"), |
| AlterTableAddColumnsStatement(Seq("table_name"), Seq( |
| QualifiedColType(Seq("x", "y", "z"), IntegerType, true, Some("doc"), None) |
| ))) |
| } |
| |
| test("alter table: add multiple columns with nested column name") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ADD COLUMN x.y.z int COMMENT 'doc', a.b string FIRST"), |
| AlterTableAddColumnsStatement(Seq("table_name"), Seq( |
| QualifiedColType(Seq("x", "y", "z"), IntegerType, true, Some("doc"), None), |
| QualifiedColType(Seq("a", "b"), StringType, true, None, Some(first())) |
| ))) |
| } |
| |
| test("alter table: set location") { |
| comparePlans( |
| parsePlan("ALTER TABLE a.b.c SET LOCATION 'new location'"), |
| AlterTableSetLocationStatement(Seq("a", "b", "c"), None, "new location")) |
| |
| comparePlans( |
| parsePlan("ALTER TABLE a.b.c PARTITION(ds='2017-06-10') SET LOCATION 'new location'"), |
| AlterTableSetLocationStatement( |
| Seq("a", "b", "c"), |
| Some(Map("ds" -> "2017-06-10")), |
| "new location")) |
| } |
| |
| test("alter table: rename column") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name RENAME COLUMN a.b.c TO d"), |
| AlterTableRenameColumnStatement( |
| Seq("table_name"), |
| Seq("a", "b", "c"), |
| "d")) |
| } |
| |
| test("alter table: update column type using ALTER") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ALTER COLUMN a.b.c TYPE bigint"), |
| AlterTableAlterColumnStatement( |
| Seq("table_name"), |
| Seq("a", "b", "c"), |
| Some(LongType), |
| None, |
| None, |
| None)) |
| } |
| |
| test("alter table: update column type invalid type") { |
| val msg = intercept[ParseException] { |
| parsePlan("ALTER TABLE table_name ALTER COLUMN a.b.c TYPE bad_type") |
| }.getMessage |
| assert(msg.contains("DataType bad_type is not supported")) |
| } |
| |
| test("alter table: update column type") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name CHANGE COLUMN a.b.c TYPE bigint"), |
| AlterTableAlterColumnStatement( |
| Seq("table_name"), |
| Seq("a", "b", "c"), |
| Some(LongType), |
| None, |
| None, |
| None)) |
| } |
| |
| test("alter table: update column comment") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name CHANGE COLUMN a.b.c COMMENT 'new comment'"), |
| AlterTableAlterColumnStatement( |
| Seq("table_name"), |
| Seq("a", "b", "c"), |
| None, |
| None, |
| Some("new comment"), |
| None)) |
| } |
| |
| test("alter table: update column position") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name CHANGE COLUMN a.b.c FIRST"), |
| AlterTableAlterColumnStatement( |
| Seq("table_name"), |
| Seq("a", "b", "c"), |
| None, |
| None, |
| None, |
| Some(first()))) |
| } |
| |
| test("alter table: mutiple property changes are not allowed") { |
| intercept[ParseException] { |
| parsePlan("ALTER TABLE table_name ALTER COLUMN a.b.c " + |
| "TYPE bigint COMMENT 'new comment'")} |
| |
| intercept[ParseException] { |
| parsePlan("ALTER TABLE table_name ALTER COLUMN a.b.c " + |
| "TYPE bigint COMMENT AFTER d")} |
| |
| intercept[ParseException] { |
| parsePlan("ALTER TABLE table_name ALTER COLUMN a.b.c " + |
| "TYPE bigint COMMENT 'new comment' AFTER d")} |
| } |
| |
| test("alter table: SET/DROP NOT NULL") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ALTER COLUMN a.b.c SET NOT NULL"), |
| AlterTableAlterColumnStatement( |
| Seq("table_name"), |
| Seq("a", "b", "c"), |
| None, |
| Some(false), |
| None, |
| None)) |
| |
| comparePlans( |
| parsePlan("ALTER TABLE table_name ALTER COLUMN a.b.c DROP NOT NULL"), |
| AlterTableAlterColumnStatement( |
| Seq("table_name"), |
| Seq("a", "b", "c"), |
| None, |
| Some(true), |
| None, |
| None)) |
| } |
| |
| test("alter table: drop column") { |
| comparePlans( |
| parsePlan("ALTER TABLE table_name DROP COLUMN a.b.c"), |
| AlterTableDropColumnsStatement(Seq("table_name"), Seq(Seq("a", "b", "c")))) |
| } |
| |
| test("alter table: drop multiple columns") { |
| val sql = "ALTER TABLE table_name DROP COLUMN x, y, a.b.c" |
| Seq(sql, sql.replace("COLUMN", "COLUMNS")).foreach { drop => |
| comparePlans( |
| parsePlan(drop), |
| AlterTableDropColumnsStatement( |
| Seq("table_name"), |
| Seq(Seq("x"), Seq("y"), Seq("a", "b", "c")))) |
| } |
| } |
| |
| test("alter table: hive style change column") { |
| val sql1 = "ALTER TABLE table_name CHANGE COLUMN a.b.c c INT" |
| val sql2 = "ALTER TABLE table_name CHANGE COLUMN a.b.c c INT COMMENT 'new_comment'" |
| val sql3 = "ALTER TABLE table_name CHANGE COLUMN a.b.c c INT AFTER other_col" |
| |
| comparePlans( |
| parsePlan(sql1), |
| AlterTableAlterColumnStatement( |
| Seq("table_name"), |
| Seq("a", "b", "c"), |
| Some(IntegerType), |
| None, |
| None, |
| None)) |
| |
| comparePlans( |
| parsePlan(sql2), |
| AlterTableAlterColumnStatement( |
| Seq("table_name"), |
| Seq("a", "b", "c"), |
| Some(IntegerType), |
| None, |
| Some("new_comment"), |
| None)) |
| |
| comparePlans( |
| parsePlan(sql3), |
| AlterTableAlterColumnStatement( |
| Seq("table_name"), |
| Seq("a", "b", "c"), |
| Some(IntegerType), |
| None, |
| None, |
| Some(after("other_col")))) |
| |
| // renaming column not supported in hive style ALTER COLUMN. |
| intercept("ALTER TABLE table_name CHANGE COLUMN a.b.c new_name INT", |
| "please run RENAME COLUMN instead") |
| |
| // ALTER COLUMN for a partition is not supported. |
| intercept("ALTER TABLE table_name PARTITION (a='1') CHANGE COLUMN a.b.c c INT") |
| } |
| |
| test("alter table: hive style replace columns") { |
| val sql1 = "ALTER TABLE table_name REPLACE COLUMNS (x string)" |
| val sql2 = "ALTER TABLE table_name REPLACE COLUMNS (x string COMMENT 'x1')" |
| val sql3 = "ALTER TABLE table_name REPLACE COLUMNS (x string COMMENT 'x1', y int)" |
| val sql4 = "ALTER TABLE table_name REPLACE COLUMNS (x string COMMENT 'x1', y int COMMENT 'y1')" |
| |
| comparePlans( |
| parsePlan(sql1), |
| AlterTableReplaceColumnsStatement( |
| Seq("table_name"), |
| Seq(QualifiedColType(Seq("x"), StringType, true, None, None)))) |
| |
| comparePlans( |
| parsePlan(sql2), |
| AlterTableReplaceColumnsStatement( |
| Seq("table_name"), |
| Seq(QualifiedColType(Seq("x"), StringType, true, Some("x1"), None)))) |
| |
| comparePlans( |
| parsePlan(sql3), |
| AlterTableReplaceColumnsStatement( |
| Seq("table_name"), |
| Seq( |
| QualifiedColType(Seq("x"), StringType, true, Some("x1"), None), |
| QualifiedColType(Seq("y"), IntegerType, true, None, None) |
| ))) |
| |
| comparePlans( |
| parsePlan(sql4), |
| AlterTableReplaceColumnsStatement( |
| Seq("table_name"), |
| Seq( |
| QualifiedColType(Seq("x"), StringType, true, Some("x1"), None), |
| QualifiedColType(Seq("y"), IntegerType, true, Some("y1"), None) |
| ))) |
| |
| intercept("ALTER TABLE table_name PARTITION (a='1') REPLACE COLUMNS (x string)", |
| "Operation not allowed: ALTER TABLE table PARTITION partition_spec REPLACE COLUMNS") |
| |
| intercept("ALTER TABLE table_name REPLACE COLUMNS (x string NOT NULL)", |
| "NOT NULL is not supported in Hive-style REPLACE COLUMNS") |
| |
| intercept("ALTER TABLE table_name REPLACE COLUMNS (x string FIRST)", |
| "Column position is not supported in Hive-style REPLACE COLUMNS") |
| } |
| |
| test("alter table/view: rename table/view") { |
| comparePlans( |
| parsePlan("ALTER TABLE a.b.c RENAME TO x.y.z"), |
| RenameTableStatement(Seq("a", "b", "c"), Seq("x", "y", "z"), isView = false)) |
| comparePlans( |
| parsePlan("ALTER VIEW a.b.c RENAME TO x.y.z"), |
| RenameTableStatement(Seq("a", "b", "c"), Seq("x", "y", "z"), isView = true)) |
| } |
| |
| test("describe table column") { |
| comparePlans(parsePlan("DESCRIBE t col"), |
| DescribeColumn( |
| UnresolvedTableOrView(Seq("t")), Seq("col"), isExtended = false)) |
| comparePlans(parsePlan("DESCRIBE t `abc.xyz`"), |
| DescribeColumn( |
| UnresolvedTableOrView(Seq("t")), Seq("abc.xyz"), isExtended = false)) |
| comparePlans(parsePlan("DESCRIBE t abc.xyz"), |
| DescribeColumn( |
| UnresolvedTableOrView(Seq("t")), Seq("abc", "xyz"), isExtended = false)) |
| comparePlans(parsePlan("DESCRIBE t `a.b`.`x.y`"), |
| DescribeColumn( |
| UnresolvedTableOrView(Seq("t")), Seq("a.b", "x.y"), isExtended = false)) |
| |
| comparePlans(parsePlan("DESCRIBE TABLE t col"), |
| DescribeColumn( |
| UnresolvedTableOrView(Seq("t")), Seq("col"), isExtended = false)) |
| comparePlans(parsePlan("DESCRIBE TABLE EXTENDED t col"), |
| DescribeColumn( |
| UnresolvedTableOrView(Seq("t")), Seq("col"), isExtended = true)) |
| comparePlans(parsePlan("DESCRIBE TABLE FORMATTED t col"), |
| DescribeColumn( |
| UnresolvedTableOrView(Seq("t")), Seq("col"), isExtended = true)) |
| |
| val caught = intercept[AnalysisException]( |
| parsePlan("DESCRIBE TABLE t PARTITION (ds='1970-01-01') col")) |
| assert(caught.getMessage.contains( |
| "DESC TABLE COLUMN for a specific partition is not supported")) |
| } |
| |
| test("describe database") { |
| val sql1 = "DESCRIBE DATABASE EXTENDED a.b" |
| val sql2 = "DESCRIBE DATABASE a.b" |
| comparePlans(parsePlan(sql1), |
| DescribeNamespace(UnresolvedNamespace(Seq("a", "b")), extended = true)) |
| comparePlans(parsePlan(sql2), |
| DescribeNamespace(UnresolvedNamespace(Seq("a", "b")), extended = false)) |
| } |
| |
| test("SPARK-17328 Fix NPE with EXPLAIN DESCRIBE TABLE") { |
| comparePlans(parsePlan("describe t"), |
| DescribeRelation(UnresolvedTableOrView(Seq("t")), Map.empty, isExtended = false)) |
| comparePlans(parsePlan("describe table t"), |
| DescribeRelation(UnresolvedTableOrView(Seq("t")), Map.empty, isExtended = false)) |
| comparePlans(parsePlan("describe table extended t"), |
| DescribeRelation(UnresolvedTableOrView(Seq("t")), Map.empty, isExtended = true)) |
| comparePlans(parsePlan("describe table formatted t"), |
| DescribeRelation(UnresolvedTableOrView(Seq("t")), Map.empty, isExtended = true)) |
| } |
| |
| test("insert table: basic append") { |
| Seq( |
| "INSERT INTO TABLE testcat.ns1.ns2.tbl SELECT * FROM source", |
| "INSERT INTO testcat.ns1.ns2.tbl SELECT * FROM source" |
| ).foreach { sql => |
| parseCompare(sql, |
| InsertIntoStatement( |
| UnresolvedRelation(Seq("testcat", "ns1", "ns2", "tbl")), |
| Map.empty, |
| Project(Seq(UnresolvedStar(None)), UnresolvedRelation(Seq("source"))), |
| overwrite = false, ifPartitionNotExists = false)) |
| } |
| } |
| |
| test("insert table: append from another catalog") { |
| parseCompare("INSERT INTO TABLE testcat.ns1.ns2.tbl SELECT * FROM testcat2.db.tbl", |
| InsertIntoStatement( |
| UnresolvedRelation(Seq("testcat", "ns1", "ns2", "tbl")), |
| Map.empty, |
| Project(Seq(UnresolvedStar(None)), UnresolvedRelation(Seq("testcat2", "db", "tbl"))), |
| overwrite = false, ifPartitionNotExists = false)) |
| } |
| |
| test("insert table: append with partition") { |
| parseCompare( |
| """ |
| |INSERT INTO testcat.ns1.ns2.tbl |
| |PARTITION (p1 = 3, p2) |
| |SELECT * FROM source |
| """.stripMargin, |
| InsertIntoStatement( |
| UnresolvedRelation(Seq("testcat", "ns1", "ns2", "tbl")), |
| Map("p1" -> Some("3"), "p2" -> None), |
| Project(Seq(UnresolvedStar(None)), UnresolvedRelation(Seq("source"))), |
| overwrite = false, ifPartitionNotExists = false)) |
| } |
| |
| test("insert table: overwrite") { |
| Seq( |
| "INSERT OVERWRITE TABLE testcat.ns1.ns2.tbl SELECT * FROM source", |
| "INSERT OVERWRITE testcat.ns1.ns2.tbl SELECT * FROM source" |
| ).foreach { sql => |
| parseCompare(sql, |
| InsertIntoStatement( |
| UnresolvedRelation(Seq("testcat", "ns1", "ns2", "tbl")), |
| Map.empty, |
| Project(Seq(UnresolvedStar(None)), UnresolvedRelation(Seq("source"))), |
| overwrite = true, ifPartitionNotExists = false)) |
| } |
| } |
| |
| test("insert table: overwrite with partition") { |
| parseCompare( |
| """ |
| |INSERT OVERWRITE TABLE testcat.ns1.ns2.tbl |
| |PARTITION (p1 = 3, p2) |
| |SELECT * FROM source |
| """.stripMargin, |
| InsertIntoStatement( |
| UnresolvedRelation(Seq("testcat", "ns1", "ns2", "tbl")), |
| Map("p1" -> Some("3"), "p2" -> None), |
| Project(Seq(UnresolvedStar(None)), UnresolvedRelation(Seq("source"))), |
| overwrite = true, ifPartitionNotExists = false)) |
| } |
| |
| test("insert table: overwrite with partition if not exists") { |
| parseCompare( |
| """ |
| |INSERT OVERWRITE TABLE testcat.ns1.ns2.tbl |
| |PARTITION (p1 = 3) IF NOT EXISTS |
| |SELECT * FROM source |
| """.stripMargin, |
| InsertIntoStatement( |
| UnresolvedRelation(Seq("testcat", "ns1", "ns2", "tbl")), |
| Map("p1" -> Some("3")), |
| Project(Seq(UnresolvedStar(None)), UnresolvedRelation(Seq("source"))), |
| overwrite = true, ifPartitionNotExists = true)) |
| } |
| |
| test("insert table: if not exists with dynamic partition fails") { |
| val exc = intercept[AnalysisException] { |
| parsePlan( |
| """ |
| |INSERT OVERWRITE TABLE testcat.ns1.ns2.tbl |
| |PARTITION (p1 = 3, p2) IF NOT EXISTS |
| |SELECT * FROM source |
| """.stripMargin) |
| } |
| |
| assert(exc.getMessage.contains("IF NOT EXISTS with dynamic partitions")) |
| assert(exc.getMessage.contains("p2")) |
| } |
| |
| test("insert table: if not exists without overwrite fails") { |
| val exc = intercept[AnalysisException] { |
| parsePlan( |
| """ |
| |INSERT INTO TABLE testcat.ns1.ns2.tbl |
| |PARTITION (p1 = 3) IF NOT EXISTS |
| |SELECT * FROM source |
| """.stripMargin) |
| } |
| |
| assert(exc.getMessage.contains("INSERT INTO ... IF NOT EXISTS")) |
| } |
| |
| test("delete from table: delete all") { |
| parseCompare("DELETE FROM testcat.ns1.ns2.tbl", |
| DeleteFromTable( |
| UnresolvedRelation(Seq("testcat", "ns1", "ns2", "tbl")), |
| None)) |
| } |
| |
| test("delete from table: with alias and where clause") { |
| parseCompare("DELETE FROM testcat.ns1.ns2.tbl AS t WHERE t.a = 2", |
| DeleteFromTable( |
| SubqueryAlias("t", UnresolvedRelation(Seq("testcat", "ns1", "ns2", "tbl"))), |
| Some(EqualTo(UnresolvedAttribute("t.a"), Literal(2))))) |
| } |
| |
| test("delete from table: columns aliases is not allowed") { |
| val exc = intercept[ParseException] { |
| parsePlan("DELETE FROM testcat.ns1.ns2.tbl AS t(a,b,c,d) WHERE d = 2") |
| } |
| |
| assert(exc.getMessage.contains("Columns aliases are not allowed in DELETE.")) |
| } |
| |
| test("update table: basic") { |
| parseCompare( |
| """ |
| |UPDATE testcat.ns1.ns2.tbl |
| |SET a='Robert', b=32 |
| """.stripMargin, |
| UpdateTable( |
| UnresolvedRelation(Seq("testcat", "ns1", "ns2", "tbl")), |
| Seq(Assignment(UnresolvedAttribute("a"), Literal("Robert")), |
| Assignment(UnresolvedAttribute("b"), Literal(32))), |
| None)) |
| } |
| |
| test("update table: with alias and where clause") { |
| parseCompare( |
| """ |
| |UPDATE testcat.ns1.ns2.tbl AS t |
| |SET t.a='Robert', t.b=32 |
| |WHERE t.c=2 |
| """.stripMargin, |
| UpdateTable( |
| SubqueryAlias("t", UnresolvedRelation(Seq("testcat", "ns1", "ns2", "tbl"))), |
| Seq(Assignment(UnresolvedAttribute("t.a"), Literal("Robert")), |
| Assignment(UnresolvedAttribute("t.b"), Literal(32))), |
| Some(EqualTo(UnresolvedAttribute("t.c"), Literal(2))))) |
| } |
| |
| test("update table: columns aliases is not allowed") { |
| val exc = intercept[ParseException] { |
| parsePlan( |
| """ |
| |UPDATE testcat.ns1.ns2.tbl AS t(a,b,c,d) |
| |SET b='Robert', c=32 |
| |WHERE d=2 |
| """.stripMargin) |
| } |
| |
| assert(exc.getMessage.contains("Columns aliases are not allowed in UPDATE.")) |
| } |
| |
| test("merge into table: basic") { |
| parseCompare( |
| """ |
| |MERGE INTO testcat1.ns1.ns2.tbl AS target |
| |USING testcat2.ns1.ns2.tbl AS source |
| |ON target.col1 = source.col1 |
| |WHEN MATCHED AND (target.col2='delete') THEN DELETE |
| |WHEN MATCHED AND (target.col2='update') THEN UPDATE SET target.col2 = source.col2 |
| |WHEN NOT MATCHED AND (target.col2='insert') |
| |THEN INSERT (target.col1, target.col2) values (source.col1, source.col2) |
| """.stripMargin, |
| MergeIntoTable( |
| SubqueryAlias("target", UnresolvedRelation(Seq("testcat1", "ns1", "ns2", "tbl"))), |
| SubqueryAlias("source", UnresolvedRelation(Seq("testcat2", "ns1", "ns2", "tbl"))), |
| EqualTo(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Seq(DeleteAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("delete")))), |
| UpdateAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("update"))), |
| Seq(Assignment(UnresolvedAttribute("target.col2"), |
| UnresolvedAttribute("source.col2"))))), |
| Seq(InsertAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("insert"))), |
| Seq(Assignment(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Assignment(UnresolvedAttribute("target.col2"), UnresolvedAttribute("source.col2"))))))) |
| } |
| |
| test("merge into table: using subquery") { |
| parseCompare( |
| """ |
| |MERGE INTO testcat1.ns1.ns2.tbl AS target |
| |USING (SELECT * FROM testcat2.ns1.ns2.tbl) AS source |
| |ON target.col1 = source.col1 |
| |WHEN MATCHED AND (target.col2='delete') THEN DELETE |
| |WHEN MATCHED AND (target.col2='update') THEN UPDATE SET target.col2 = source.col2 |
| |WHEN NOT MATCHED AND (target.col2='insert') |
| |THEN INSERT (target.col1, target.col2) values (source.col1, source.col2) |
| """.stripMargin, |
| MergeIntoTable( |
| SubqueryAlias("target", UnresolvedRelation(Seq("testcat1", "ns1", "ns2", "tbl"))), |
| SubqueryAlias("source", Project(Seq(UnresolvedStar(None)), |
| UnresolvedRelation(Seq("testcat2", "ns1", "ns2", "tbl")))), |
| EqualTo(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Seq(DeleteAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("delete")))), |
| UpdateAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("update"))), |
| Seq(Assignment(UnresolvedAttribute("target.col2"), |
| UnresolvedAttribute("source.col2"))))), |
| Seq(InsertAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("insert"))), |
| Seq(Assignment(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Assignment(UnresolvedAttribute("target.col2"), UnresolvedAttribute("source.col2"))))))) |
| } |
| |
| test("merge into table: cte") { |
| parseCompare( |
| """ |
| |MERGE INTO testcat1.ns1.ns2.tbl AS target |
| |USING (WITH s as (SELECT * FROM testcat2.ns1.ns2.tbl) SELECT * FROM s) AS source |
| |ON target.col1 = source.col1 |
| |WHEN MATCHED AND (target.col2='delete') THEN DELETE |
| |WHEN MATCHED AND (target.col2='update') THEN UPDATE SET target.col2 = source.col2 |
| |WHEN NOT MATCHED AND (target.col2='insert') |
| |THEN INSERT (target.col1, target.col2) values (source.col1, source.col2) |
| """.stripMargin, |
| MergeIntoTable( |
| SubqueryAlias("target", UnresolvedRelation(Seq("testcat1", "ns1", "ns2", "tbl"))), |
| SubqueryAlias("source", With(Project(Seq(UnresolvedStar(None)), |
| UnresolvedRelation(Seq("s"))), |
| Seq("s" -> SubqueryAlias("s", Project(Seq(UnresolvedStar(None)), |
| UnresolvedRelation(Seq("testcat2", "ns1", "ns2", "tbl"))))))), |
| EqualTo(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Seq(DeleteAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("delete")))), |
| UpdateAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("update"))), |
| Seq(Assignment(UnresolvedAttribute("target.col2"), |
| UnresolvedAttribute("source.col2"))))), |
| Seq(InsertAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("insert"))), |
| Seq(Assignment(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Assignment(UnresolvedAttribute("target.col2"), UnresolvedAttribute("source.col2"))))))) |
| } |
| |
| test("merge into table: no additional condition") { |
| parseCompare( |
| """ |
| |MERGE INTO testcat1.ns1.ns2.tbl AS target |
| |USING testcat2.ns1.ns2.tbl AS source |
| |ON target.col1 = source.col1 |
| |WHEN MATCHED THEN UPDATE SET target.col2 = source.col2 |
| |WHEN NOT MATCHED |
| |THEN INSERT (target.col1, target.col2) values (source.col1, source.col2) |
| """.stripMargin, |
| MergeIntoTable( |
| SubqueryAlias("target", UnresolvedRelation(Seq("testcat1", "ns1", "ns2", "tbl"))), |
| SubqueryAlias("source", UnresolvedRelation(Seq("testcat2", "ns1", "ns2", "tbl"))), |
| EqualTo(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Seq(UpdateAction(None, |
| Seq(Assignment(UnresolvedAttribute("target.col2"), UnresolvedAttribute("source.col2"))))), |
| Seq(InsertAction(None, |
| Seq(Assignment(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Assignment(UnresolvedAttribute("target.col2"), UnresolvedAttribute("source.col2"))))))) |
| } |
| |
| test("merge into table: star") { |
| parseCompare( |
| """ |
| |MERGE INTO testcat1.ns1.ns2.tbl AS target |
| |USING testcat2.ns1.ns2.tbl AS source |
| |ON target.col1 = source.col1 |
| |WHEN MATCHED AND (target.col2='delete') THEN DELETE |
| |WHEN MATCHED AND (target.col2='update') THEN UPDATE SET * |
| |WHEN NOT MATCHED AND (target.col2='insert') |
| |THEN INSERT * |
| """.stripMargin, |
| MergeIntoTable( |
| SubqueryAlias("target", UnresolvedRelation(Seq("testcat1", "ns1", "ns2", "tbl"))), |
| SubqueryAlias("source", UnresolvedRelation(Seq("testcat2", "ns1", "ns2", "tbl"))), |
| EqualTo(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Seq(DeleteAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("delete")))), |
| UpdateAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("update"))), Seq())), |
| Seq(InsertAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("insert"))), |
| Seq())))) |
| } |
| |
| test("merge into table: columns aliases are not allowed") { |
| Seq("target(c1, c2)" -> "source", "target" -> "source(c1, c2)").foreach { |
| case (targetAlias, sourceAlias) => |
| val exc = intercept[ParseException] { |
| parsePlan( |
| s""" |
| |MERGE INTO testcat1.ns1.ns2.tbl AS $targetAlias |
| |USING testcat2.ns1.ns2.tbl AS $sourceAlias |
| |ON target.col1 = source.col1 |
| |WHEN MATCHED AND (target.col2='delete') THEN DELETE |
| |WHEN MATCHED AND (target.col2='update') THEN UPDATE SET target.col2 = source.col2 |
| |WHEN NOT MATCHED AND (target.col2='insert') |
| |THEN INSERT (target.col1, target.col2) values (source.col1, source.col2) |
| """.stripMargin) |
| } |
| |
| assert(exc.getMessage.contains("Columns aliases are not allowed in MERGE.")) |
| } |
| } |
| |
| test("merge into table: multi matched and not matched clauses") { |
| parseCompare( |
| """ |
| |MERGE INTO testcat1.ns1.ns2.tbl AS target |
| |USING testcat2.ns1.ns2.tbl AS source |
| |ON target.col1 = source.col1 |
| |WHEN MATCHED AND (target.col2='delete') THEN DELETE |
| |WHEN MATCHED AND (target.col2='update1') THEN UPDATE SET target.col2 = 1 |
| |WHEN MATCHED AND (target.col2='update2') THEN UPDATE SET target.col2 = 2 |
| |WHEN NOT MATCHED AND (target.col2='insert1') |
| |THEN INSERT (target.col1, target.col2) values (source.col1, 1) |
| |WHEN NOT MATCHED AND (target.col2='insert2') |
| |THEN INSERT (target.col1, target.col2) values (source.col1, 2) |
| """.stripMargin, |
| MergeIntoTable( |
| SubqueryAlias("target", UnresolvedRelation(Seq("testcat1", "ns1", "ns2", "tbl"))), |
| SubqueryAlias("source", UnresolvedRelation(Seq("testcat2", "ns1", "ns2", "tbl"))), |
| EqualTo(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Seq(DeleteAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("delete")))), |
| UpdateAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("update1"))), |
| Seq(Assignment(UnresolvedAttribute("target.col2"), Literal(1)))), |
| UpdateAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("update2"))), |
| Seq(Assignment(UnresolvedAttribute("target.col2"), Literal(2))))), |
| Seq(InsertAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("insert1"))), |
| Seq(Assignment(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Assignment(UnresolvedAttribute("target.col2"), Literal(1)))), |
| InsertAction(Some(EqualTo(UnresolvedAttribute("target.col2"), Literal("insert2"))), |
| Seq(Assignment(UnresolvedAttribute("target.col1"), UnresolvedAttribute("source.col1")), |
| Assignment(UnresolvedAttribute("target.col2"), Literal(2))))))) |
| } |
| |
| test("merge into table: only the last matched clause can omit the condition") { |
| val exc = intercept[ParseException] { |
| parsePlan( |
| """ |
| |MERGE INTO testcat1.ns1.ns2.tbl AS target |
| |USING testcat2.ns1.ns2.tbl AS source |
| |ON target.col1 = source.col1 |
| |WHEN MATCHED AND (target.col2 == 'update1') THEN UPDATE SET target.col2 = 1 |
| |WHEN MATCHED THEN UPDATE SET target.col2 = 2 |
| |WHEN MATCHED THEN DELETE |
| |WHEN NOT MATCHED AND (target.col2='insert') |
| |THEN INSERT (target.col1, target.col2) values (source.col1, source.col2) |
| """.stripMargin) |
| } |
| |
| assert(exc.getMessage.contains("only the last MATCHED clause can omit the condition")) |
| } |
| |
| test("merge into table: only the last not matched clause can omit the condition") { |
| val exc = intercept[ParseException] { |
| parsePlan( |
| """ |
| |MERGE INTO testcat1.ns1.ns2.tbl AS target |
| |USING testcat2.ns1.ns2.tbl AS source |
| |ON target.col1 = source.col1 |
| |WHEN MATCHED AND (target.col2 == 'update') THEN UPDATE SET target.col2 = source.col2 |
| |WHEN MATCHED THEN DELETE |
| |WHEN NOT MATCHED AND (target.col2='insert1') |
| |THEN INSERT (target.col1, target.col2) values (source.col1, 1) |
| |WHEN NOT MATCHED |
| |THEN INSERT (target.col1, target.col2) values (source.col1, 2) |
| |WHEN NOT MATCHED |
| |THEN INSERT (target.col1, target.col2) values (source.col1, source.col2) |
| """.stripMargin) |
| } |
| |
| assert(exc.getMessage.contains("only the last NOT MATCHED clause can omit the condition")) |
| } |
| |
| test("merge into table: there must be a when (not) matched condition") { |
| val exc = intercept[ParseException] { |
| parsePlan( |
| """ |
| |MERGE INTO testcat1.ns1.ns2.tbl AS target |
| |USING testcat2.ns1.ns2.tbl AS source |
| |ON target.col1 = source.col1 |
| """.stripMargin) |
| } |
| |
| assert(exc.getMessage.contains("There must be at least one WHEN clause in a MERGE statement")) |
| } |
| |
| test("show tables") { |
| comparePlans( |
| parsePlan("SHOW TABLES"), |
| ShowTables(UnresolvedNamespace(Seq.empty[String]), None)) |
| comparePlans( |
| parsePlan("SHOW TABLES '*test*'"), |
| ShowTables(UnresolvedNamespace(Seq.empty[String]), Some("*test*"))) |
| comparePlans( |
| parsePlan("SHOW TABLES LIKE '*test*'"), |
| ShowTables(UnresolvedNamespace(Seq.empty[String]), Some("*test*"))) |
| comparePlans( |
| parsePlan("SHOW TABLES FROM testcat.ns1.ns2.tbl"), |
| ShowTables(UnresolvedNamespace(Seq("testcat", "ns1", "ns2", "tbl")), None)) |
| comparePlans( |
| parsePlan("SHOW TABLES IN testcat.ns1.ns2.tbl"), |
| ShowTables(UnresolvedNamespace(Seq("testcat", "ns1", "ns2", "tbl")), None)) |
| comparePlans( |
| parsePlan("SHOW TABLES IN ns1 '*test*'"), |
| ShowTables(UnresolvedNamespace(Seq("ns1")), Some("*test*"))) |
| comparePlans( |
| parsePlan("SHOW TABLES IN ns1 LIKE '*test*'"), |
| ShowTables(UnresolvedNamespace(Seq("ns1")), Some("*test*"))) |
| } |
| |
| test("show table extended") { |
| comparePlans( |
| parsePlan("SHOW TABLE EXTENDED LIKE '*test*'"), |
| ShowTableStatement(None, "*test*", None)) |
| comparePlans( |
| parsePlan("SHOW TABLE EXTENDED FROM testcat.ns1.ns2 LIKE '*test*'"), |
| ShowTableStatement(Some(Seq("testcat", "ns1", "ns2")), "*test*", None)) |
| comparePlans( |
| parsePlan("SHOW TABLE EXTENDED IN testcat.ns1.ns2 LIKE '*test*'"), |
| ShowTableStatement(Some(Seq("testcat", "ns1", "ns2")), "*test*", None)) |
| comparePlans( |
| parsePlan("SHOW TABLE EXTENDED LIKE '*test*' PARTITION(ds='2008-04-09', hr=11)"), |
| ShowTableStatement(None, "*test*", Some(Map("ds" -> "2008-04-09", "hr" -> "11")))) |
| comparePlans( |
| parsePlan("SHOW TABLE EXTENDED FROM testcat.ns1.ns2 LIKE '*test*' " + |
| "PARTITION(ds='2008-04-09')"), |
| ShowTableStatement(Some(Seq("testcat", "ns1", "ns2")), "*test*", |
| Some(Map("ds" -> "2008-04-09")))) |
| comparePlans( |
| parsePlan("SHOW TABLE EXTENDED IN testcat.ns1.ns2 LIKE '*test*' " + |
| "PARTITION(ds='2008-04-09')"), |
| ShowTableStatement(Some(Seq("testcat", "ns1", "ns2")), "*test*", |
| Some(Map("ds" -> "2008-04-09")))) |
| } |
| |
| test("show views") { |
| comparePlans( |
| parsePlan("SHOW VIEWS"), |
| ShowViews(UnresolvedNamespace(Seq.empty[String]), None)) |
| comparePlans( |
| parsePlan("SHOW VIEWS '*test*'"), |
| ShowViews(UnresolvedNamespace(Seq.empty[String]), Some("*test*"))) |
| comparePlans( |
| parsePlan("SHOW VIEWS LIKE '*test*'"), |
| ShowViews(UnresolvedNamespace(Seq.empty[String]), Some("*test*"))) |
| comparePlans( |
| parsePlan("SHOW VIEWS FROM testcat.ns1.ns2.tbl"), |
| ShowViews(UnresolvedNamespace(Seq("testcat", "ns1", "ns2", "tbl")), None)) |
| comparePlans( |
| parsePlan("SHOW VIEWS IN testcat.ns1.ns2.tbl"), |
| ShowViews(UnresolvedNamespace(Seq("testcat", "ns1", "ns2", "tbl")), None)) |
| comparePlans( |
| parsePlan("SHOW VIEWS IN ns1 '*test*'"), |
| ShowViews(UnresolvedNamespace(Seq("ns1")), Some("*test*"))) |
| comparePlans( |
| parsePlan("SHOW VIEWS IN ns1 LIKE '*test*'"), |
| ShowViews(UnresolvedNamespace(Seq("ns1")), Some("*test*"))) |
| } |
| |
| test("create namespace -- backward compatibility with DATABASE/DBPROPERTIES") { |
| val expected = CreateNamespaceStatement( |
| Seq("a", "b", "c"), |
| ifNotExists = true, |
| Map( |
| "a" -> "a", |
| "b" -> "b", |
| "c" -> "c", |
| "comment" -> "namespace_comment", |
| "location" -> "/home/user/db")) |
| |
| comparePlans( |
| parsePlan( |
| """ |
| |CREATE NAMESPACE IF NOT EXISTS a.b.c |
| |WITH PROPERTIES ('a'='a', 'b'='b', 'c'='c') |
| |COMMENT 'namespace_comment' LOCATION '/home/user/db' |
| """.stripMargin), |
| expected) |
| |
| comparePlans( |
| parsePlan( |
| """ |
| |CREATE DATABASE IF NOT EXISTS a.b.c |
| |WITH DBPROPERTIES ('a'='a', 'b'='b', 'c'='c') |
| |COMMENT 'namespace_comment' LOCATION '/home/user/db' |
| """.stripMargin), |
| expected) |
| } |
| |
| test("create namespace -- check duplicates") { |
| def createDatabase(duplicateClause: String): String = { |
| s""" |
| |CREATE NAMESPACE IF NOT EXISTS a.b.c |
| |$duplicateClause |
| |$duplicateClause |
| """.stripMargin |
| } |
| val sql1 = createDatabase("COMMENT 'namespace_comment'") |
| val sql2 = createDatabase("LOCATION '/home/user/db'") |
| val sql3 = createDatabase("WITH PROPERTIES ('a'='a', 'b'='b', 'c'='c')") |
| val sql4 = createDatabase("WITH DBPROPERTIES ('a'='a', 'b'='b', 'c'='c')") |
| |
| intercept(sql1, "Found duplicate clauses: COMMENT") |
| intercept(sql2, "Found duplicate clauses: LOCATION") |
| intercept(sql3, "Found duplicate clauses: WITH PROPERTIES") |
| intercept(sql4, "Found duplicate clauses: WITH DBPROPERTIES") |
| } |
| |
| test("create namespace - property values must be set") { |
| assertUnsupported( |
| sql = "CREATE NAMESPACE a.b.c WITH PROPERTIES('key_without_value', 'key_with_value'='x')", |
| containsThesePhrases = Seq("key_without_value")) |
| } |
| |
| test("create namespace -- either PROPERTIES or DBPROPERTIES is allowed") { |
| val sql = |
| s""" |
| |CREATE NAMESPACE IF NOT EXISTS a.b.c |
| |WITH PROPERTIES ('a'='a', 'b'='b', 'c'='c') |
| |WITH DBPROPERTIES ('a'='a', 'b'='b', 'c'='c') |
| """.stripMargin |
| intercept(sql, "Either PROPERTIES or DBPROPERTIES is allowed") |
| } |
| |
| test("create namespace - support for other types in PROPERTIES") { |
| val sql = |
| """ |
| |CREATE NAMESPACE a.b.c |
| |LOCATION '/home/user/db' |
| |WITH PROPERTIES ('a'=1, 'b'=0.1, 'c'=TRUE) |
| """.stripMargin |
| comparePlans( |
| parsePlan(sql), |
| CreateNamespaceStatement( |
| Seq("a", "b", "c"), |
| ifNotExists = false, |
| Map( |
| "a" -> "1", |
| "b" -> "0.1", |
| "c" -> "true", |
| "location" -> "/home/user/db"))) |
| } |
| |
| test("drop namespace") { |
| comparePlans( |
| parsePlan("DROP NAMESPACE a.b.c"), |
| DropNamespace( |
| UnresolvedNamespace(Seq("a", "b", "c")), ifExists = false, cascade = false)) |
| |
| comparePlans( |
| parsePlan("DROP NAMESPACE IF EXISTS a.b.c"), |
| DropNamespace( |
| UnresolvedNamespace(Seq("a", "b", "c")), ifExists = true, cascade = false)) |
| |
| comparePlans( |
| parsePlan("DROP NAMESPACE IF EXISTS a.b.c RESTRICT"), |
| DropNamespace( |
| UnresolvedNamespace(Seq("a", "b", "c")), ifExists = true, cascade = false)) |
| |
| comparePlans( |
| parsePlan("DROP NAMESPACE IF EXISTS a.b.c CASCADE"), |
| DropNamespace( |
| UnresolvedNamespace(Seq("a", "b", "c")), ifExists = true, cascade = true)) |
| |
| comparePlans( |
| parsePlan("DROP NAMESPACE a.b.c CASCADE"), |
| DropNamespace( |
| UnresolvedNamespace(Seq("a", "b", "c")), ifExists = false, cascade = true)) |
| } |
| |
| test("set namespace properties") { |
| comparePlans( |
| parsePlan("ALTER DATABASE a.b.c SET PROPERTIES ('a'='a', 'b'='b', 'c'='c')"), |
| AlterNamespaceSetProperties( |
| UnresolvedNamespace(Seq("a", "b", "c")), Map("a" -> "a", "b" -> "b", "c" -> "c"))) |
| |
| comparePlans( |
| parsePlan("ALTER SCHEMA a.b.c SET PROPERTIES ('a'='a')"), |
| AlterNamespaceSetProperties( |
| UnresolvedNamespace(Seq("a", "b", "c")), Map("a" -> "a"))) |
| |
| comparePlans( |
| parsePlan("ALTER NAMESPACE a.b.c SET PROPERTIES ('b'='b')"), |
| AlterNamespaceSetProperties( |
| UnresolvedNamespace(Seq("a", "b", "c")), Map("b" -> "b"))) |
| |
| comparePlans( |
| parsePlan("ALTER DATABASE a.b.c SET DBPROPERTIES ('a'='a', 'b'='b', 'c'='c')"), |
| AlterNamespaceSetProperties( |
| UnresolvedNamespace(Seq("a", "b", "c")), Map("a" -> "a", "b" -> "b", "c" -> "c"))) |
| |
| comparePlans( |
| parsePlan("ALTER SCHEMA a.b.c SET DBPROPERTIES ('a'='a')"), |
| AlterNamespaceSetProperties( |
| UnresolvedNamespace(Seq("a", "b", "c")), Map("a" -> "a"))) |
| |
| comparePlans( |
| parsePlan("ALTER NAMESPACE a.b.c SET DBPROPERTIES ('b'='b')"), |
| AlterNamespaceSetProperties( |
| UnresolvedNamespace(Seq("a", "b", "c")), Map("b" -> "b"))) |
| } |
| |
| test("set namespace location") { |
| comparePlans( |
| parsePlan("ALTER DATABASE a.b.c SET LOCATION '/home/user/db'"), |
| AlterNamespaceSetLocation( |
| UnresolvedNamespace(Seq("a", "b", "c")), "/home/user/db")) |
| |
| comparePlans( |
| parsePlan("ALTER SCHEMA a.b.c SET LOCATION '/home/user/db'"), |
| AlterNamespaceSetLocation( |
| UnresolvedNamespace(Seq("a", "b", "c")), "/home/user/db")) |
| |
| comparePlans( |
| parsePlan("ALTER NAMESPACE a.b.c SET LOCATION '/home/user/db'"), |
| AlterNamespaceSetLocation( |
| UnresolvedNamespace(Seq("a", "b", "c")), "/home/user/db")) |
| } |
| |
| test("show databases: basic") { |
| comparePlans( |
| parsePlan("SHOW DATABASES"), |
| ShowNamespaces(UnresolvedNamespace(Seq.empty[String]), None)) |
| comparePlans( |
| parsePlan("SHOW DATABASES LIKE 'defau*'"), |
| ShowNamespaces(UnresolvedNamespace(Seq.empty[String]), Some("defau*"))) |
| } |
| |
| test("show databases: FROM/IN operator is not allowed") { |
| def verify(sql: String): Unit = { |
| val exc = intercept[ParseException] { parsePlan(sql) } |
| assert(exc.getMessage.contains("FROM/IN operator is not allowed in SHOW DATABASES")) |
| } |
| |
| verify("SHOW DATABASES FROM testcat.ns1.ns2") |
| verify("SHOW DATABASES IN testcat.ns1.ns2") |
| } |
| |
| test("show namespaces") { |
| comparePlans( |
| parsePlan("SHOW NAMESPACES"), |
| ShowNamespaces(UnresolvedNamespace(Seq.empty[String]), None)) |
| comparePlans( |
| parsePlan("SHOW NAMESPACES FROM testcat.ns1.ns2"), |
| ShowNamespaces(UnresolvedNamespace(Seq("testcat", "ns1", "ns2")), None)) |
| comparePlans( |
| parsePlan("SHOW NAMESPACES IN testcat.ns1.ns2"), |
| ShowNamespaces(UnresolvedNamespace(Seq("testcat", "ns1", "ns2")), None)) |
| comparePlans( |
| parsePlan("SHOW NAMESPACES IN testcat.ns1 LIKE '*pattern*'"), |
| ShowNamespaces(UnresolvedNamespace(Seq("testcat", "ns1")), Some("*pattern*"))) |
| } |
| |
| test("analyze table statistics") { |
| comparePlans(parsePlan("analyze table a.b.c compute statistics"), |
| AnalyzeTableStatement(Seq("a", "b", "c"), Map.empty, noScan = false)) |
| comparePlans(parsePlan("analyze table a.b.c compute statistics noscan"), |
| AnalyzeTableStatement(Seq("a", "b", "c"), Map.empty, noScan = true)) |
| comparePlans(parsePlan("analyze table a.b.c partition (a) compute statistics nOscAn"), |
| AnalyzeTableStatement(Seq("a", "b", "c"), Map("a" -> None), noScan = true)) |
| |
| // Partitions specified |
| comparePlans( |
| parsePlan("ANALYZE TABLE a.b.c PARTITION(ds='2008-04-09', hr=11) COMPUTE STATISTICS"), |
| AnalyzeTableStatement( |
| Seq("a", "b", "c"), Map("ds" -> Some("2008-04-09"), "hr" -> Some("11")), noScan = false)) |
| comparePlans( |
| parsePlan("ANALYZE TABLE a.b.c PARTITION(ds='2008-04-09', hr=11) COMPUTE STATISTICS noscan"), |
| AnalyzeTableStatement( |
| Seq("a", "b", "c"), Map("ds" -> Some("2008-04-09"), "hr" -> Some("11")), noScan = true)) |
| comparePlans( |
| parsePlan("ANALYZE TABLE a.b.c PARTITION(ds='2008-04-09') COMPUTE STATISTICS noscan"), |
| AnalyzeTableStatement(Seq("a", "b", "c"), Map("ds" -> Some("2008-04-09")), noScan = true)) |
| comparePlans( |
| parsePlan("ANALYZE TABLE a.b.c PARTITION(ds='2008-04-09', hr) COMPUTE STATISTICS"), |
| AnalyzeTableStatement( |
| Seq("a", "b", "c"), Map("ds" -> Some("2008-04-09"), "hr" -> None), noScan = false)) |
| comparePlans( |
| parsePlan("ANALYZE TABLE a.b.c PARTITION(ds='2008-04-09', hr) COMPUTE STATISTICS noscan"), |
| AnalyzeTableStatement( |
| Seq("a", "b", "c"), Map("ds" -> Some("2008-04-09"), "hr" -> None), noScan = true)) |
| comparePlans( |
| parsePlan("ANALYZE TABLE a.b.c PARTITION(ds, hr=11) COMPUTE STATISTICS noscan"), |
| AnalyzeTableStatement( |
| Seq("a", "b", "c"), Map("ds" -> None, "hr" -> Some("11")), noScan = true)) |
| comparePlans( |
| parsePlan("ANALYZE TABLE a.b.c PARTITION(ds, hr) COMPUTE STATISTICS"), |
| AnalyzeTableStatement(Seq("a", "b", "c"), Map("ds" -> None, "hr" -> None), noScan = false)) |
| comparePlans( |
| parsePlan("ANALYZE TABLE a.b.c PARTITION(ds, hr) COMPUTE STATISTICS noscan"), |
| AnalyzeTableStatement(Seq("a", "b", "c"), Map("ds" -> None, "hr" -> None), noScan = true)) |
| |
| intercept("analyze table a.b.c compute statistics xxxx", |
| "Expected `NOSCAN` instead of `xxxx`") |
| intercept("analyze table a.b.c partition (a) compute statistics xxxx", |
| "Expected `NOSCAN` instead of `xxxx`") |
| } |
| |
| test("analyze table column statistics") { |
| intercept("ANALYZE TABLE a.b.c COMPUTE STATISTICS FOR COLUMNS", "") |
| |
| comparePlans( |
| parsePlan("ANALYZE TABLE a.b.c COMPUTE STATISTICS FOR COLUMNS key, value"), |
| AnalyzeColumnStatement(Seq("a", "b", "c"), Option(Seq("key", "value")), allColumns = false)) |
| |
| // Partition specified - should be ignored |
| comparePlans( |
| parsePlan( |
| s""" |
| |ANALYZE TABLE a.b.c PARTITION(ds='2017-06-10') |
| |COMPUTE STATISTICS FOR COLUMNS key, value |
| """.stripMargin), |
| AnalyzeColumnStatement(Seq("a", "b", "c"), Option(Seq("key", "value")), allColumns = false)) |
| |
| // Partition specified should be ignored in case of COMPUTE STATISTICS FOR ALL COLUMNS |
| comparePlans( |
| parsePlan( |
| s""" |
| |ANALYZE TABLE a.b.c PARTITION(ds='2017-06-10') |
| |COMPUTE STATISTICS FOR ALL COLUMNS |
| """.stripMargin), |
| AnalyzeColumnStatement(Seq("a", "b", "c"), None, allColumns = true)) |
| |
| intercept("ANALYZE TABLE a.b.c COMPUTE STATISTICS FOR ALL COLUMNS key, value", |
| "mismatched input 'key' expecting {<EOF>, ';'}") |
| intercept("ANALYZE TABLE a.b.c COMPUTE STATISTICS FOR ALL", |
| "missing 'COLUMNS' at '<EOF>'") |
| } |
| |
| test("MSCK REPAIR TABLE") { |
| comparePlans( |
| parsePlan("MSCK REPAIR TABLE a.b.c"), |
| RepairTableStatement(Seq("a", "b", "c"))) |
| } |
| |
| test("LOAD DATA INTO table") { |
| comparePlans( |
| parsePlan("LOAD DATA INPATH 'filepath' INTO TABLE a.b.c"), |
| LoadDataStatement(Seq("a", "b", "c"), "filepath", false, false, None)) |
| |
| comparePlans( |
| parsePlan("LOAD DATA LOCAL INPATH 'filepath' INTO TABLE a.b.c"), |
| LoadDataStatement(Seq("a", "b", "c"), "filepath", true, false, None)) |
| |
| comparePlans( |
| parsePlan("LOAD DATA LOCAL INPATH 'filepath' OVERWRITE INTO TABLE a.b.c"), |
| LoadDataStatement(Seq("a", "b", "c"), "filepath", true, true, None)) |
| |
| comparePlans( |
| parsePlan( |
| s""" |
| |LOAD DATA LOCAL INPATH 'filepath' OVERWRITE INTO TABLE a.b.c |
| |PARTITION(ds='2017-06-10') |
| """.stripMargin), |
| LoadDataStatement( |
| Seq("a", "b", "c"), |
| "filepath", |
| true, |
| true, |
| Some(Map("ds" -> "2017-06-10")))) |
| } |
| |
| test("SHOW CREATE table") { |
| comparePlans( |
| parsePlan("SHOW CREATE TABLE a.b.c"), |
| ShowCreateTableStatement(Seq("a", "b", "c"))) |
| } |
| |
| test("CACHE TABLE") { |
| comparePlans( |
| parsePlan("CACHE TABLE a.b.c"), |
| CacheTableStatement(Seq("a", "b", "c"), None, false, Map.empty)) |
| |
| comparePlans( |
| parsePlan("CACHE LAZY TABLE a.b.c"), |
| CacheTableStatement(Seq("a", "b", "c"), None, true, Map.empty)) |
| |
| comparePlans( |
| parsePlan("CACHE LAZY TABLE a.b.c OPTIONS('storageLevel' 'DISK_ONLY')"), |
| CacheTableStatement(Seq("a", "b", "c"), None, true, Map("storageLevel" -> "DISK_ONLY"))) |
| |
| intercept("CACHE TABLE a.b.c AS SELECT * FROM testData", |
| "It is not allowed to add catalog/namespace prefix a.b") |
| } |
| |
| test("UNCACHE TABLE") { |
| comparePlans( |
| parsePlan("UNCACHE TABLE a.b.c"), |
| UncacheTableStatement(Seq("a", "b", "c"), ifExists = false)) |
| |
| comparePlans( |
| parsePlan("UNCACHE TABLE IF EXISTS a.b.c"), |
| UncacheTableStatement(Seq("a", "b", "c"), ifExists = true)) |
| } |
| |
| test("TRUNCATE table") { |
| comparePlans( |
| parsePlan("TRUNCATE TABLE a.b.c"), |
| TruncateTableStatement(Seq("a", "b", "c"), None)) |
| |
| comparePlans( |
| parsePlan("TRUNCATE TABLE a.b.c PARTITION(ds='2017-06-10')"), |
| TruncateTableStatement(Seq("a", "b", "c"), Some(Map("ds" -> "2017-06-10")))) |
| } |
| |
| test("SHOW PARTITIONS") { |
| val sql1 = "SHOW PARTITIONS t1" |
| val sql2 = "SHOW PARTITIONS db1.t1" |
| val sql3 = "SHOW PARTITIONS t1 PARTITION(partcol1='partvalue', partcol2='partvalue')" |
| val sql4 = "SHOW PARTITIONS a.b.c" |
| val sql5 = "SHOW PARTITIONS a.b.c PARTITION(ds='2017-06-10')" |
| |
| val parsed1 = parsePlan(sql1) |
| val expected1 = ShowPartitionsStatement(Seq("t1"), None) |
| val parsed2 = parsePlan(sql2) |
| val expected2 = ShowPartitionsStatement(Seq("db1", "t1"), None) |
| val parsed3 = parsePlan(sql3) |
| val expected3 = ShowPartitionsStatement(Seq("t1"), |
| Some(Map("partcol1" -> "partvalue", "partcol2" -> "partvalue"))) |
| val parsed4 = parsePlan(sql4) |
| val expected4 = ShowPartitionsStatement(Seq("a", "b", "c"), None) |
| val parsed5 = parsePlan(sql5) |
| val expected5 = ShowPartitionsStatement(Seq("a", "b", "c"), Some(Map("ds" -> "2017-06-10"))) |
| |
| comparePlans(parsed1, expected1) |
| comparePlans(parsed2, expected2) |
| comparePlans(parsed3, expected3) |
| comparePlans(parsed4, expected4) |
| comparePlans(parsed5, expected5) |
| } |
| |
| test("REFRESH TABLE") { |
| comparePlans( |
| parsePlan("REFRESH TABLE a.b.c"), |
| RefreshTable(UnresolvedTableOrView(Seq("a", "b", "c")))) |
| } |
| |
| test("show columns") { |
| val sql1 = "SHOW COLUMNS FROM t1" |
| val sql2 = "SHOW COLUMNS IN db1.t1" |
| val sql3 = "SHOW COLUMNS FROM t1 IN db1" |
| val sql4 = "SHOW COLUMNS FROM db1.t1 IN db1" |
| |
| val parsed1 = parsePlan(sql1) |
| val expected1 = ShowColumnsStatement(Seq("t1"), None) |
| val parsed2 = parsePlan(sql2) |
| val expected2 = ShowColumnsStatement(Seq("db1", "t1"), None) |
| val parsed3 = parsePlan(sql3) |
| val expected3 = ShowColumnsStatement(Seq("t1"), Some(Seq("db1"))) |
| val parsed4 = parsePlan(sql4) |
| val expected4 = ShowColumnsStatement(Seq("db1", "t1"), Some(Seq("db1"))) |
| |
| comparePlans(parsed1, expected1) |
| comparePlans(parsed2, expected2) |
| comparePlans(parsed3, expected3) |
| comparePlans(parsed4, expected4) |
| } |
| |
| test("alter table: recover partitions") { |
| comparePlans( |
| parsePlan("ALTER TABLE a.b.c RECOVER PARTITIONS"), |
| AlterTableRecoverPartitionsStatement(Seq("a", "b", "c"))) |
| } |
| |
| test("alter table: add partition") { |
| val sql1 = |
| """ |
| |ALTER TABLE a.b.c ADD IF NOT EXISTS PARTITION |
| |(dt='2008-08-08', country='us') LOCATION 'location1' PARTITION |
| |(dt='2009-09-09', country='uk') |
| """.stripMargin |
| val sql2 = "ALTER TABLE a.b.c ADD PARTITION (dt='2008-08-08') LOCATION 'loc'" |
| |
| val parsed1 = parsePlan(sql1) |
| val parsed2 = parsePlan(sql2) |
| |
| val expected1 = AlterTableAddPartitionStatement( |
| Seq("a", "b", "c"), |
| Seq( |
| (Map("dt" -> "2008-08-08", "country" -> "us"), Some("location1")), |
| (Map("dt" -> "2009-09-09", "country" -> "uk"), None)), |
| ifNotExists = true) |
| val expected2 = AlterTableAddPartitionStatement( |
| Seq("a", "b", "c"), |
| Seq((Map("dt" -> "2008-08-08"), Some("loc"))), |
| ifNotExists = false) |
| |
| comparePlans(parsed1, expected1) |
| comparePlans(parsed2, expected2) |
| } |
| |
| test("alter view: add partition (not supported)") { |
| assertUnsupported( |
| """ |
| |ALTER VIEW a.b.c ADD IF NOT EXISTS PARTITION |
| |(dt='2008-08-08', country='us') PARTITION |
| |(dt='2009-09-09', country='uk') |
| """.stripMargin) |
| } |
| |
| test("alter table: rename partition") { |
| val sql1 = |
| """ |
| |ALTER TABLE table_name PARTITION (dt='2008-08-08', country='us') |
| |RENAME TO PARTITION (dt='2008-09-09', country='uk') |
| """.stripMargin |
| val parsed1 = parsePlan(sql1) |
| val expected1 = AlterTableRenamePartitionStatement( |
| Seq("table_name"), |
| Map("dt" -> "2008-08-08", "country" -> "us"), |
| Map("dt" -> "2008-09-09", "country" -> "uk")) |
| comparePlans(parsed1, expected1) |
| |
| val sql2 = |
| """ |
| |ALTER TABLE a.b.c PARTITION (ds='2017-06-10') |
| |RENAME TO PARTITION (ds='2018-06-10') |
| """.stripMargin |
| val parsed2 = parsePlan(sql2) |
| val expected2 = AlterTableRenamePartitionStatement( |
| Seq("a", "b", "c"), |
| Map("ds" -> "2017-06-10"), |
| Map("ds" -> "2018-06-10")) |
| comparePlans(parsed2, expected2) |
| } |
| |
| // ALTER TABLE table_name DROP [IF EXISTS] PARTITION spec1[, PARTITION spec2, ...] |
| // ALTER VIEW table_name DROP [IF EXISTS] PARTITION spec1[, PARTITION spec2, ...] |
| test("alter table: drop partition") { |
| val sql1_table = |
| """ |
| |ALTER TABLE table_name DROP IF EXISTS PARTITION |
| |(dt='2008-08-08', country='us'), PARTITION (dt='2009-09-09', country='uk') |
| """.stripMargin |
| val sql2_table = |
| """ |
| |ALTER TABLE table_name DROP PARTITION |
| |(dt='2008-08-08', country='us'), PARTITION (dt='2009-09-09', country='uk') |
| """.stripMargin |
| val sql1_view = sql1_table.replace("TABLE", "VIEW") |
| val sql2_view = sql2_table.replace("TABLE", "VIEW") |
| |
| val parsed1_table = parsePlan(sql1_table) |
| val parsed2_table = parsePlan(sql2_table) |
| val parsed1_purge = parsePlan(sql1_table + " PURGE") |
| |
| assertUnsupported(sql1_view) |
| assertUnsupported(sql2_view) |
| |
| val expected1_table = AlterTableDropPartitionStatement( |
| Seq("table_name"), |
| Seq( |
| Map("dt" -> "2008-08-08", "country" -> "us"), |
| Map("dt" -> "2009-09-09", "country" -> "uk")), |
| ifExists = true, |
| purge = false, |
| retainData = false) |
| val expected2_table = expected1_table.copy(ifExists = false) |
| val expected1_purge = expected1_table.copy(purge = true) |
| |
| comparePlans(parsed1_table, expected1_table) |
| comparePlans(parsed2_table, expected2_table) |
| comparePlans(parsed1_purge, expected1_purge) |
| |
| val sql3_table = "ALTER TABLE a.b.c DROP IF EXISTS PARTITION (ds='2017-06-10')" |
| val expected3_table = AlterTableDropPartitionStatement( |
| Seq("a", "b", "c"), |
| Seq(Map("ds" -> "2017-06-10")), |
| ifExists = true, |
| purge = false, |
| retainData = false) |
| |
| val parsed3_table = parsePlan(sql3_table) |
| comparePlans(parsed3_table, expected3_table) |
| } |
| |
| test("show current namespace") { |
| comparePlans( |
| parsePlan("SHOW CURRENT NAMESPACE"), |
| ShowCurrentNamespaceStatement()) |
| } |
| |
| test("alter table: SerDe properties") { |
| val sql1 = "ALTER TABLE table_name SET SERDE 'org.apache.class'" |
| val parsed1 = parsePlan(sql1) |
| val expected1 = AlterTableSerDePropertiesStatement( |
| Seq("table_name"), Some("org.apache.class"), None, None) |
| comparePlans(parsed1, expected1) |
| |
| val sql2 = |
| """ |
| |ALTER TABLE table_name SET SERDE 'org.apache.class' |
| |WITH SERDEPROPERTIES ('columns'='foo,bar', 'field.delim' = ',') |
| """.stripMargin |
| val parsed2 = parsePlan(sql2) |
| val expected2 = AlterTableSerDePropertiesStatement( |
| Seq("table_name"), |
| Some("org.apache.class"), |
| Some(Map("columns" -> "foo,bar", "field.delim" -> ",")), |
| None) |
| comparePlans(parsed2, expected2) |
| |
| val sql3 = |
| """ |
| |ALTER TABLE table_name |
| |SET SERDEPROPERTIES ('columns'='foo,bar', 'field.delim' = ',') |
| """.stripMargin |
| val parsed3 = parsePlan(sql3) |
| val expected3 = AlterTableSerDePropertiesStatement( |
| Seq("table_name"), None, Some(Map("columns" -> "foo,bar", "field.delim" -> ",")), None) |
| comparePlans(parsed3, expected3) |
| |
| val sql4 = |
| """ |
| |ALTER TABLE table_name PARTITION (test=1, dt='2008-08-08', country='us') |
| |SET SERDE 'org.apache.class' |
| |WITH SERDEPROPERTIES ('columns'='foo,bar', 'field.delim' = ',') |
| """.stripMargin |
| val parsed4 = parsePlan(sql4) |
| val expected4 = AlterTableSerDePropertiesStatement( |
| Seq("table_name"), |
| Some("org.apache.class"), |
| Some(Map("columns" -> "foo,bar", "field.delim" -> ",")), |
| Some(Map("test" -> "1", "dt" -> "2008-08-08", "country" -> "us"))) |
| comparePlans(parsed4, expected4) |
| |
| val sql5 = |
| """ |
| |ALTER TABLE table_name PARTITION (test=1, dt='2008-08-08', country='us') |
| |SET SERDEPROPERTIES ('columns'='foo,bar', 'field.delim' = ',') |
| """.stripMargin |
| val parsed5 = parsePlan(sql5) |
| val expected5 = AlterTableSerDePropertiesStatement( |
| Seq("table_name"), |
| None, |
| Some(Map("columns" -> "foo,bar", "field.delim" -> ",")), |
| Some(Map("test" -> "1", "dt" -> "2008-08-08", "country" -> "us"))) |
| comparePlans(parsed5, expected5) |
| |
| val sql6 = |
| """ |
| |ALTER TABLE a.b.c SET SERDE 'org.apache.class' |
| |WITH SERDEPROPERTIES ('columns'='foo,bar', 'field.delim' = ',') |
| """.stripMargin |
| val parsed6 = parsePlan(sql6) |
| val expected6 = AlterTableSerDePropertiesStatement( |
| Seq("a", "b", "c"), |
| Some("org.apache.class"), |
| Some(Map("columns" -> "foo,bar", "field.delim" -> ",")), |
| None) |
| comparePlans(parsed6, expected6) |
| |
| val sql7 = |
| """ |
| |ALTER TABLE a.b.c PARTITION (test=1, dt='2008-08-08', country='us') |
| |SET SERDEPROPERTIES ('columns'='foo,bar', 'field.delim' = ',') |
| """.stripMargin |
| val parsed7 = parsePlan(sql7) |
| val expected7 = AlterTableSerDePropertiesStatement( |
| Seq("a", "b", "c"), |
| None, |
| Some(Map("columns" -> "foo,bar", "field.delim" -> ",")), |
| Some(Map("test" -> "1", "dt" -> "2008-08-08", "country" -> "us"))) |
| comparePlans(parsed7, expected7) |
| } |
| |
| test("alter view: AS Query") { |
| val parsed = parsePlan("ALTER VIEW a.b.c AS SELECT 1") |
| val expected = AlterViewAsStatement( |
| Seq("a", "b", "c"), "SELECT 1", parsePlan("SELECT 1")) |
| comparePlans(parsed, expected) |
| } |
| |
| test("create view -- basic") { |
| val v1 = "CREATE VIEW view1 AS SELECT * FROM tab1" |
| val parsed1 = parsePlan(v1) |
| |
| val expected1 = CreateViewStatement( |
| Seq("view1"), |
| Seq.empty[(String, Option[String])], |
| None, |
| Map.empty[String, String], |
| Some("SELECT * FROM tab1"), |
| parsePlan("SELECT * FROM tab1"), |
| false, |
| false, |
| PersistedView) |
| comparePlans(parsed1, expected1) |
| |
| val v2 = "CREATE TEMPORARY VIEW a.b.c AS SELECT * FROM tab1" |
| val parsed2 = parsePlan(v2) |
| |
| val expected2 = CreateViewStatement( |
| Seq("a", "b", "c"), |
| Seq.empty[(String, Option[String])], |
| None, |
| Map.empty[String, String], |
| Some("SELECT * FROM tab1"), |
| parsePlan("SELECT * FROM tab1"), |
| false, |
| false, |
| LocalTempView) |
| comparePlans(parsed2, expected2) |
| } |
| |
| test("create view - full") { |
| val v1 = |
| """ |
| |CREATE OR REPLACE VIEW view1 |
| |(col1, col3 COMMENT 'hello') |
| |TBLPROPERTIES('prop1Key'="prop1Val") |
| |COMMENT 'BLABLA' |
| |AS SELECT * FROM tab1 |
| """.stripMargin |
| val parsed1 = parsePlan(v1) |
| val expected1 = CreateViewStatement( |
| Seq("view1"), |
| Seq("col1" -> None, "col3" -> Some("hello")), |
| Some("BLABLA"), |
| Map("prop1Key" -> "prop1Val"), |
| Some("SELECT * FROM tab1"), |
| parsePlan("SELECT * FROM tab1"), |
| false, |
| true, |
| PersistedView) |
| comparePlans(parsed1, expected1) |
| |
| val v2 = |
| """ |
| |CREATE OR REPLACE GLOBAL TEMPORARY VIEW a.b.c |
| |(col1, col3 COMMENT 'hello') |
| |COMMENT 'BLABLA' |
| |AS SELECT * FROM tab1 |
| """.stripMargin |
| val parsed2 = parsePlan(v2) |
| val expected2 = CreateViewStatement( |
| Seq("a", "b", "c"), |
| Seq("col1" -> None, "col3" -> Some("hello")), |
| Some("BLABLA"), |
| Map(), |
| Some("SELECT * FROM tab1"), |
| parsePlan("SELECT * FROM tab1"), |
| false, |
| true, |
| GlobalTempView) |
| comparePlans(parsed2, expected2) |
| } |
| |
| test("create view -- partitioned view") { |
| val v1 = "CREATE VIEW view1 partitioned on (ds, hr) as select * from srcpart" |
| intercept[ParseException] { |
| parsePlan(v1) |
| } |
| } |
| |
| test("create view - duplicate clauses") { |
| def createViewStatement(duplicateClause: String): String = { |
| s""" |
| |CREATE OR REPLACE VIEW view1 |
| |(col1, col3 COMMENT 'hello') |
| |$duplicateClause |
| |$duplicateClause |
| |AS SELECT * FROM tab1 |
| """.stripMargin |
| } |
| val sql1 = createViewStatement("COMMENT 'BLABLA'") |
| val sql2 = createViewStatement("TBLPROPERTIES('prop1Key'=\"prop1Val\")") |
| intercept(sql1, "Found duplicate clauses: COMMENT") |
| intercept(sql2, "Found duplicate clauses: TBLPROPERTIES") |
| } |
| |
| test("SPARK-32374: create temporary view with properties not allowed") { |
| assertUnsupported( |
| sql = """ |
| |CREATE OR REPLACE TEMPORARY VIEW a.b.c |
| |(col1, col3 COMMENT 'hello') |
| |TBLPROPERTIES('prop1Key'="prop1Val") |
| |AS SELECT * FROM tab1 |
| """.stripMargin, |
| containsThesePhrases = Seq("TBLPROPERTIES can't coexist with CREATE TEMPORARY VIEW")) |
| } |
| |
| test("SHOW TBLPROPERTIES table") { |
| comparePlans( |
| parsePlan("SHOW TBLPROPERTIES a.b.c"), |
| ShowTableProperties(UnresolvedTableOrView(Seq("a", "b", "c")), None)) |
| |
| comparePlans( |
| parsePlan("SHOW TBLPROPERTIES a.b.c('propKey1')"), |
| ShowTableProperties(UnresolvedTableOrView(Seq("a", "b", "c")), Some("propKey1"))) |
| } |
| |
| test("DESCRIBE FUNCTION") { |
| comparePlans( |
| parsePlan("DESC FUNCTION a"), |
| DescribeFunction(UnresolvedFunc(Seq("a")), false)) |
| comparePlans( |
| parsePlan("DESCRIBE FUNCTION a"), |
| DescribeFunction(UnresolvedFunc(Seq("a")), false)) |
| comparePlans( |
| parsePlan("DESCRIBE FUNCTION a.b.c"), |
| DescribeFunction(UnresolvedFunc(Seq("a", "b", "c")), false)) |
| comparePlans( |
| parsePlan("DESCRIBE FUNCTION EXTENDED a.b.c"), |
| DescribeFunction(UnresolvedFunc(Seq("a", "b", "c")), true)) |
| } |
| |
| test("SHOW FUNCTIONS") { |
| comparePlans( |
| parsePlan("SHOW FUNCTIONS"), |
| ShowFunctions(None, true, true, None)) |
| comparePlans( |
| parsePlan("SHOW USER FUNCTIONS"), |
| ShowFunctions(None, true, false, None)) |
| comparePlans( |
| parsePlan("SHOW user FUNCTIONS"), |
| ShowFunctions(None, true, false, None)) |
| comparePlans( |
| parsePlan("SHOW SYSTEM FUNCTIONS"), |
| ShowFunctions(None, false, true, None)) |
| comparePlans( |
| parsePlan("SHOW ALL FUNCTIONS"), |
| ShowFunctions(None, true, true, None)) |
| comparePlans( |
| parsePlan("SHOW FUNCTIONS LIKE 'funct*'"), |
| ShowFunctions(None, true, true, Some("funct*"))) |
| comparePlans( |
| parsePlan("SHOW FUNCTIONS LIKE a.b.c"), |
| ShowFunctions(Some(UnresolvedFunc(Seq("a", "b", "c"))), true, true, None)) |
| val sql = "SHOW other FUNCTIONS" |
| intercept(sql, s"$sql not supported") |
| } |
| |
| test("DROP FUNCTION") { |
| comparePlans( |
| parsePlan("DROP FUNCTION a"), |
| DropFunction(UnresolvedFunc(Seq("a")), false, false)) |
| comparePlans( |
| parsePlan("DROP FUNCTION a.b.c"), |
| DropFunction(UnresolvedFunc(Seq("a", "b", "c")), false, false)) |
| comparePlans( |
| parsePlan("DROP TEMPORARY FUNCTION a.b.c"), |
| DropFunction(UnresolvedFunc(Seq("a", "b", "c")), false, true)) |
| comparePlans( |
| parsePlan("DROP FUNCTION IF EXISTS a.b.c"), |
| DropFunction(UnresolvedFunc(Seq("a", "b", "c")), true, false)) |
| comparePlans( |
| parsePlan("DROP TEMPORARY FUNCTION IF EXISTS a.b.c"), |
| DropFunction(UnresolvedFunc(Seq("a", "b", "c")), true, true)) |
| } |
| |
| test("CREATE FUNCTION") { |
| parseCompare("CREATE FUNCTION a as 'fun'", |
| CreateFunctionStatement(Seq("a"), "fun", Seq(), false, false, false)) |
| |
| parseCompare("CREATE FUNCTION a.b.c as 'fun'", |
| CreateFunctionStatement(Seq("a", "b", "c"), "fun", Seq(), false, false, false)) |
| |
| parseCompare("CREATE OR REPLACE FUNCTION a.b.c as 'fun'", |
| CreateFunctionStatement(Seq("a", "b", "c"), "fun", Seq(), false, false, true)) |
| |
| parseCompare("CREATE TEMPORARY FUNCTION a.b.c as 'fun'", |
| CreateFunctionStatement(Seq("a", "b", "c"), "fun", Seq(), true, false, false)) |
| |
| parseCompare("CREATE FUNCTION IF NOT EXISTS a.b.c as 'fun'", |
| CreateFunctionStatement(Seq("a", "b", "c"), "fun", Seq(), false, true, false)) |
| |
| parseCompare("CREATE FUNCTION a as 'fun' USING JAR 'j'", |
| CreateFunctionStatement(Seq("a"), "fun", Seq(FunctionResource(JarResource, "j")), |
| false, false, false)) |
| |
| parseCompare("CREATE FUNCTION a as 'fun' USING ARCHIVE 'a'", |
| CreateFunctionStatement(Seq("a"), "fun", Seq(FunctionResource(ArchiveResource, "a")), |
| false, false, false)) |
| |
| parseCompare("CREATE FUNCTION a as 'fun' USING FILE 'f'", |
| CreateFunctionStatement(Seq("a"), "fun", Seq(FunctionResource(FileResource, "f")), |
| false, false, false)) |
| |
| parseCompare("CREATE FUNCTION a as 'fun' USING JAR 'j', ARCHIVE 'a', FILE 'f'", |
| CreateFunctionStatement(Seq("a"), "fun", Seq(FunctionResource(JarResource, "j"), |
| FunctionResource(ArchiveResource, "a"), FunctionResource(FileResource, "f")), |
| false, false, false)) |
| |
| intercept("CREATE FUNCTION a as 'fun' USING OTHER 'o'", |
| "Operation not allowed: CREATE FUNCTION with resource type 'other'") |
| } |
| |
| test("REFRESH FUNCTION") { |
| parseCompare("REFRESH FUNCTION c", |
| RefreshFunction(UnresolvedFunc(Seq("c")))) |
| parseCompare("REFRESH FUNCTION b.c", |
| RefreshFunction(UnresolvedFunc(Seq("b", "c")))) |
| parseCompare("REFRESH FUNCTION a.b.c", |
| RefreshFunction(UnresolvedFunc(Seq("a", "b", "c")))) |
| } |
| |
| private case class TableSpec( |
| name: Seq[String], |
| schema: Option[StructType], |
| partitioning: Seq[Transform], |
| bucketSpec: Option[BucketSpec], |
| properties: Map[String, String], |
| provider: Option[String], |
| options: Map[String, String], |
| location: Option[String], |
| comment: Option[String]) |
| |
| private object TableSpec { |
| def apply(plan: LogicalPlan): TableSpec = { |
| plan match { |
| case create: CreateTableStatement => |
| TableSpec( |
| create.tableName, |
| Some(create.tableSchema), |
| create.partitioning, |
| create.bucketSpec, |
| create.properties, |
| create.provider, |
| create.options, |
| create.location, |
| create.comment) |
| case replace: ReplaceTableStatement => |
| TableSpec( |
| replace.tableName, |
| Some(replace.tableSchema), |
| replace.partitioning, |
| replace.bucketSpec, |
| replace.properties, |
| replace.provider, |
| replace.options, |
| replace.location, |
| replace.comment) |
| case ctas: CreateTableAsSelectStatement => |
| TableSpec( |
| ctas.tableName, |
| Some(ctas.asSelect).filter(_.resolved).map(_.schema), |
| ctas.partitioning, |
| ctas.bucketSpec, |
| ctas.properties, |
| ctas.provider, |
| ctas.options, |
| ctas.location, |
| ctas.comment) |
| case rtas: ReplaceTableAsSelectStatement => |
| TableSpec( |
| rtas.tableName, |
| Some(rtas.asSelect).filter(_.resolved).map(_.schema), |
| rtas.partitioning, |
| rtas.bucketSpec, |
| rtas.properties, |
| rtas.provider, |
| rtas.options, |
| rtas.location, |
| rtas.comment) |
| case other => |
| fail(s"Expected to parse Create, CTAS, Replace, or RTAS plan" + |
| s" from query, got ${other.getClass.getName}.") |
| } |
| } |
| } |
| |
| test("comment on") { |
| comparePlans( |
| parsePlan("COMMENT ON DATABASE a.b.c IS NULL"), |
| CommentOnNamespace(UnresolvedNamespace(Seq("a", "b", "c")), "")) |
| |
| comparePlans( |
| parsePlan("COMMENT ON DATABASE a.b.c IS 'NULL'"), |
| CommentOnNamespace(UnresolvedNamespace(Seq("a", "b", "c")), "NULL")) |
| |
| comparePlans( |
| parsePlan("COMMENT ON NAMESPACE a.b.c IS ''"), |
| CommentOnNamespace(UnresolvedNamespace(Seq("a", "b", "c")), "")) |
| |
| comparePlans( |
| parsePlan("COMMENT ON TABLE a.b.c IS 'xYz'"), |
| CommentOnTable(UnresolvedTable(Seq("a", "b", "c")), "xYz")) |
| } |
| |
| // TODO: ignored by SPARK-31707, restore the test after create table syntax unification |
| ignore("create table - without using") { |
| val sql = "CREATE TABLE 1m.2g(a INT)" |
| val expectedTableSpec = TableSpec( |
| Seq("1m", "2g"), |
| Some(new StructType().add("a", IntegerType)), |
| Seq.empty[Transform], |
| None, |
| Map.empty[String, String], |
| None, |
| Map.empty[String, String], |
| None, |
| None) |
| |
| testCreateOrReplaceDdl(sql, expectedTableSpec, expectedIfNotExists = false) |
| } |
| } |