Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
01e4cdf
Merge remote-tracking branch 'upstream/master'
gatorsmile Nov 13, 2015
6835704
Merge remote-tracking branch 'upstream/master'
gatorsmile Nov 14, 2015
9180687
Merge remote-tracking branch 'upstream/master'
gatorsmile Nov 14, 2015
b38a21e
SPARK-11633
gatorsmile Nov 17, 2015
d2b84af
Merge remote-tracking branch 'upstream/master' into joinMakeCopy
gatorsmile Nov 17, 2015
fda8025
Merge remote-tracking branch 'upstream/master'
gatorspark Nov 17, 2015
ac0dccd
Merge branch 'master' of https://github.com/gatorsmile/spark
gatorspark Nov 17, 2015
6e0018b
Merge remote-tracking branch 'upstream/master'
Nov 20, 2015
0546772
converge
gatorsmile Nov 20, 2015
b37a64f
converge
gatorsmile Nov 20, 2015
c2a872c
Merge remote-tracking branch 'upstream/master'
gatorsmile Jan 6, 2016
ab6dbd7
Merge remote-tracking branch 'upstream/master'
gatorsmile Jan 6, 2016
4276356
Merge remote-tracking branch 'upstream/master'
gatorsmile Jan 6, 2016
2dab708
Merge remote-tracking branch 'upstream/master'
gatorsmile Jan 7, 2016
0458770
Merge remote-tracking branch 'upstream/master'
gatorsmile Jan 8, 2016
1debdfa
Merge remote-tracking branch 'upstream/master'
gatorsmile Jan 9, 2016
763706d
Merge remote-tracking branch 'upstream/master'
gatorsmile Jan 14, 2016
4de6ec1
Merge remote-tracking branch 'upstream/master'
gatorsmile Jan 18, 2016
9422a4f
Merge remote-tracking branch 'upstream/master'
gatorsmile Jan 19, 2016
52bdf48
Merge remote-tracking branch 'upstream/master'
gatorsmile Jan 20, 2016
1e95df3
Merge remote-tracking branch 'upstream/master'
gatorsmile Jan 23, 2016
fab24cf
Merge remote-tracking branch 'upstream/master'
gatorsmile Feb 1, 2016
8b2e33b
Merge remote-tracking branch 'upstream/master'
gatorsmile Feb 5, 2016
2ee1876
Merge remote-tracking branch 'upstream/master'
gatorsmile Feb 11, 2016
b9f0090
Merge remote-tracking branch 'upstream/master'
gatorsmile Feb 12, 2016
ade6f7e
Merge remote-tracking branch 'upstream/master'
gatorsmile Feb 15, 2016
9fd63d2
Merge remote-tracking branch 'upstream/master'
gatorsmile Feb 19, 2016
5199d49
Merge remote-tracking branch 'upstream/master'
gatorsmile Feb 22, 2016
404214c
Merge remote-tracking branch 'upstream/master'
gatorsmile Feb 23, 2016
c001dd9
Merge remote-tracking branch 'upstream/master'
gatorsmile Feb 25, 2016
59daa48
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 5, 2016
41d5f64
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 7, 2016
472a6e3
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 10, 2016
0fba10a
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 12, 2016
cbf73b3
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 21, 2016
c08f561
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 22, 2016
474df88
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 22, 2016
3d9828d
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 24, 2016
72d2361
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 26, 2016
b1ffb0d
native parsing support for alter view.
gatorsmile Mar 27, 2016
ea44a9f
Merge remote-tracking branch 'upstream/master' into parseAlterView
gatorsmile Mar 27, 2016
3b7b41b
added comments and fixed bugs.
gatorsmile Mar 27, 2016
7322fb1
update comments.
gatorsmile Mar 27, 2016
07afea5
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 29, 2016
8bf2007
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 30, 2016
de66cff
Merge branch 'parseAlterView' into parseAlterViewNew
gatorsmile Mar 30, 2016
9136b58
address comments.
gatorsmile Mar 30, 2016
f379636
add test cases.
gatorsmile Mar 30, 2016
38ea348
address comments.
gatorsmile Mar 31, 2016
dd34529
address comments.
gatorsmile Mar 31, 2016
48aec92
address comments.
gatorsmile Mar 31, 2016
cdb8cc0
update the comments.
gatorsmile Mar 31, 2016
315de90
address comments.
gatorsmile Mar 31, 2016
87a165b
Merge remote-tracking branch 'upstream/master'
gatorsmile Mar 31, 2016
dd1aa23
Merge branch 'parseAlterViewNew' into parseAlterViewNewNew
gatorsmile Mar 31, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ statement
(AS? query)? #createTable
| ANALYZE TABLE tableIdentifier partitionSpec? COMPUTE STATISTICS
(identifier | FOR COLUMNS identifierSeq?)? #analyze
| ALTER TABLE from=tableIdentifier RENAME TO to=tableIdentifier #renameTable
| ALTER TABLE tableIdentifier
| ALTER (TABLE | VIEW) from=tableIdentifier
RENAME TO to=tableIdentifier #renameTable
| ALTER (TABLE | VIEW) tableIdentifier
SET TBLPROPERTIES tablePropertyList #setTableProperties
| ALTER TABLE tableIdentifier
| ALTER (TABLE | VIEW) tableIdentifier
UNSET TBLPROPERTIES (IF EXISTS)? tablePropertyList #unsetTableProperties
| ALTER TABLE tableIdentifier (partitionSpec)?
SET SERDE STRING (WITH SERDEPROPERTIES tablePropertyList)? #setTableSerDe
Expand All @@ -76,12 +77,16 @@ statement
SET SKEWED LOCATION skewedLocationList #setTableSkewLocations
| ALTER TABLE tableIdentifier ADD (IF NOT EXISTS)?
partitionSpecLocation+ #addTablePartition
| ALTER VIEW tableIdentifier ADD (IF NOT EXISTS)?
partitionSpec+ #addTablePartition
| ALTER TABLE tableIdentifier
from=partitionSpec RENAME TO to=partitionSpec #renameTablePartition
| ALTER TABLE from=tableIdentifier
EXCHANGE partitionSpec WITH TABLE to=tableIdentifier #exchangeTablePartition
| ALTER TABLE tableIdentifier
DROP (IF EXISTS)? partitionSpec (',' partitionSpec)* PURGE? #dropTablePartitions
| ALTER VIEW tableIdentifier
DROP (IF EXISTS)? partitionSpec (',' partitionSpec)* #dropTablePartitions
| ALTER TABLE tableIdentifier ARCHIVE partitionSpec #archiveTablePartition
| ALTER TABLE tableIdentifier UNARCHIVE partitionSpec #unarchiveTablePartition
| ALTER TABLE tableIdentifier partitionSpec?
Expand Down Expand Up @@ -133,15 +138,6 @@ hiveNativeCommands
| DELETE FROM tableIdentifier (WHERE booleanExpression)?
| TRUNCATE TABLE tableIdentifier partitionSpec?
(COLUMNS identifierList)?
| ALTER VIEW from=tableIdentifier AS? RENAME TO to=tableIdentifier
| ALTER VIEW from=tableIdentifier AS?
SET TBLPROPERTIES tablePropertyList
| ALTER VIEW from=tableIdentifier AS?
UNSET TBLPROPERTIES (IF EXISTS)? tablePropertyList
| ALTER VIEW from=tableIdentifier AS?
ADD (IF NOT EXISTS)? partitionSpecLocation+
| ALTER VIEW from=tableIdentifier AS?
DROP (IF EXISTS)? partitionSpec (',' partitionSpec)* PURGE?
| DROP VIEW (IF EXISTS)? qualifiedName
| SHOW COLUMNS (FROM | IN) tableIdentifier ((FROM|IN) identifier)?
| START TRANSACTION (transactionMode (',' transactionMode)*)?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ class SparkSqlAstBuilder extends AstBuilder {
* For example:
* {{{
* ALTER TABLE table1 RENAME TO table2;
* ALTER VIEW view1 RENAME TO view2;
* }}}
*/
override def visitRenameTable(ctx: RenameTableContext): LogicalPlan = withOrigin(ctx) {
Expand All @@ -350,6 +351,7 @@ class SparkSqlAstBuilder extends AstBuilder {
* For example:
* {{{
* ALTER TABLE table SET TBLPROPERTIES ('comment' = new_comment);
* ALTER VIEW view SET TBLPROPERTIES ('comment' = new_comment);
* }}}
*/
override def visitSetTableProperties(
Expand All @@ -366,6 +368,7 @@ class SparkSqlAstBuilder extends AstBuilder {
* For example:
* {{{
* ALTER TABLE table UNSET TBLPROPERTIES IF EXISTS ('comment', 'key');
* ALTER VIEW view UNSET TBLPROPERTIES IF EXISTS ('comment', 'key');
* }}}
*/
override def visitUnsetTableProperties(
Expand Down Expand Up @@ -510,16 +513,22 @@ class SparkSqlAstBuilder extends AstBuilder {
* For example:
* {{{
* ALTER TABLE table ADD [IF NOT EXISTS] PARTITION spec [LOCATION 'loc1']
* ALTER VIEW view ADD [IF NOT EXISTS] PARTITION spec
* }}}
*/
override def visitAddTablePartition(
ctx: AddTablePartitionContext): LogicalPlan = withOrigin(ctx) {
// Create partition spec to location mapping.
val specsAndLocs = ctx.partitionSpecLocation.asScala.map {
splCtx =>
val spec = visitNonOptionalPartitionSpec(splCtx.partitionSpec)
val location = Option(splCtx.locationSpec).map(visitLocationSpec)
spec -> location
val specsAndLocs = if (ctx.partitionSpec.isEmpty) {
ctx.partitionSpecLocation.asScala.map {
splCtx =>
val spec = visitNonOptionalPartitionSpec(splCtx.partitionSpec)
val location = Option(splCtx.locationSpec).map(visitLocationSpec)
spec -> location
}
} else {
// Alter View: the location clauses are not allowed.
ctx.partitionSpec.asScala.map(visitNonOptionalPartitionSpec(_) -> None)
}
AlterTableAddPartition(
visitTableIdentifier(ctx.tableIdentifier),
Expand Down Expand Up @@ -568,6 +577,7 @@ class SparkSqlAstBuilder extends AstBuilder {
* For example:
* {{{
* ALTER TABLE table DROP [IF EXISTS] PARTITION spec1[, PARTITION spec2, ...] [PURGE];
* ALTER VIEW view DROP [IF EXISTS] PARTITION spec1[, PARTITION spec2, ...];
* }}}
*/
override def visitDropTablePartitions(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,16 +195,19 @@ case class DropFunction(
isTemp: Boolean)(sql: String)
extends NativeDDLCommand(sql) with Logging

/** Rename in ALTER TABLE/VIEW: change the name of a table/view to a different name. */
case class AlterTableRename(
oldName: TableIdentifier,
newName: TableIdentifier)(sql: String)
extends NativeDDLCommand(sql) with Logging

/** Set Properties in ALTER TABLE/VIEW: add metadata to a table/view. */
case class AlterTableSetProperties(
tableName: TableIdentifier,
properties: Map[String, String])(sql: String)
extends NativeDDLCommand(sql) with Logging

/** Unset Properties in ALTER TABLE/VIEW: remove metadata from a table/view. */
case class AlterTableUnsetProperties(
tableName: TableIdentifier,
properties: Map[String, String],
Expand Down Expand Up @@ -253,6 +256,12 @@ case class AlterTableSkewedLocation(
skewedMap: Map[String, String])(sql: String)
extends NativeDDLCommand(sql) with Logging

/**
* Add Partition in ALTER TABLE/VIEW: add the table/view partitions.
* 'partitionSpecsAndLocs': the syntax of ALTER VIEW is identical to ALTER TABLE,
* EXCEPT that it is ILLEGAL to specify a LOCATION clause.
* An error message will be issued if the partition exists, unless 'ifNotExists' is true.
*/
case class AlterTableAddPartition(
tableName: TableIdentifier,
partitionSpecsAndLocs: Seq[(TablePartitionSpec, Option[String])],
Expand All @@ -271,6 +280,14 @@ case class AlterTableExchangePartition(
spec: TablePartitionSpec)(sql: String)
extends NativeDDLCommand(sql) with Logging

/**
* Drop Partition in ALTER TABLE/VIEW: to drop a particular partition for a table/view.
* This removes the data and metadata for this partition.
* The data is actually moved to the .Trash/Current directory if Trash is configured,
* unless 'purge' is true, but the metadata is completely lost.
* An error message will be issued if the partition does not exist, unless 'ifExists' is true.
* Note: purge is always false when the target is a view.
*/
case class AlterTableDropPartition(
tableName: TableIdentifier,
specs: Seq[TablePartitionSpec],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,33 +195,60 @@ class DDLCommandSuite extends PlanTest {
comparePlans(parsed4, expected4)
}

test("alter table: rename table") {
val sql = "ALTER TABLE table_name RENAME TO new_table_name"
val parsed = parser.parsePlan(sql)
val expected = AlterTableRename(
// ALTER TABLE table_name RENAME TO new_table_name;
// ALTER VIEW view_name RENAME TO new_view_name;
test("alter table/view: rename table/view") {
val sql_table = "ALTER TABLE table_name RENAME TO new_table_name"
val sql_view = sql_table.replace("TABLE", "VIEW")
val parsed_table = parser.parsePlan(sql_table)
val parsed_view = parser.parsePlan(sql_view)
val expected_table = AlterTableRename(
TableIdentifier("table_name", None),
TableIdentifier("new_table_name", None))(sql)
comparePlans(parsed, expected)
TableIdentifier("new_table_name", None))(sql_table)
val expected_view = AlterTableRename(
TableIdentifier("table_name", None),
TableIdentifier("new_table_name", None))(sql_view)
comparePlans(parsed_table, expected_table)
comparePlans(parsed_view, expected_view)
}

test("alter table: alter table properties") {
val sql1 = "ALTER TABLE table_name SET TBLPROPERTIES ('test' = 'test', " +
// ALTER TABLE table_name SET TBLPROPERTIES ('comment' = new_comment);
// ALTER TABLE table_name UNSET TBLPROPERTIES [IF EXISTS] ('comment', 'key');
// ALTER VIEW view_name SET TBLPROPERTIES ('comment' = new_comment);
// ALTER VIEW view_name UNSET TBLPROPERTIES [IF EXISTS] ('comment', 'key');
test("alter table/view: alter table/view properties") {
val sql1_table = "ALTER TABLE table_name SET TBLPROPERTIES ('test' = 'test', " +
"'comment' = 'new_comment')"
val sql2 = "ALTER TABLE table_name UNSET TBLPROPERTIES ('comment', 'test')"
val sql3 = "ALTER TABLE table_name UNSET TBLPROPERTIES IF EXISTS ('comment', 'test')"
val parsed1 = parser.parsePlan(sql1)
val parsed2 = parser.parsePlan(sql2)
val parsed3 = parser.parsePlan(sql3)
val sql2_table = "ALTER TABLE table_name UNSET TBLPROPERTIES ('comment', 'test')"
val sql3_table = "ALTER TABLE table_name UNSET TBLPROPERTIES IF EXISTS ('comment', 'test')"
val sql1_view = sql1_table.replace("TABLE", "VIEW")
val sql2_view = sql2_table.replace("TABLE", "VIEW")
val sql3_view = sql3_table.replace("TABLE", "VIEW")

val parsed1_table = parser.parsePlan(sql1_table)
val parsed2_table = parser.parsePlan(sql2_table)
val parsed3_table = parser.parsePlan(sql3_table)
val parsed1_view = parser.parsePlan(sql1_view)
val parsed2_view = parser.parsePlan(sql2_view)
val parsed3_view = parser.parsePlan(sql3_view)

val tableIdent = TableIdentifier("table_name", None)
val expected1 = AlterTableSetProperties(
tableIdent, Map("test" -> "test", "comment" -> "new_comment"))(sql1)
val expected2 = AlterTableUnsetProperties(
tableIdent, Map("comment" -> null, "test" -> null), ifExists = false)(sql2)
val expected3 = AlterTableUnsetProperties(
tableIdent, Map("comment" -> null, "test" -> null), ifExists = true)(sql3)
comparePlans(parsed1, expected1)
comparePlans(parsed2, expected2)
comparePlans(parsed3, expected3)
val expected1_table = AlterTableSetProperties(
tableIdent, Map("test" -> "test", "comment" -> "new_comment"))(sql1_table)
val expected2_table = AlterTableUnsetProperties(
tableIdent, Map("comment" -> null, "test" -> null), ifExists = false)(sql2_table)
val expected3_table = AlterTableUnsetProperties(
tableIdent, Map("comment" -> null, "test" -> null), ifExists = true)(sql3_table)
val expected1_view = expected1_table.copy()(sql = sql1_view)
val expected2_view = expected2_table.copy()(sql = sql2_view)
val expected3_view = expected3_table.copy()(sql = sql3_view)

comparePlans(parsed1_table, expected1_table)
comparePlans(parsed2_table, expected2_table)
comparePlans(parsed3_table, expected3_table)
comparePlans(parsed1_view, expected1_view)
comparePlans(parsed2_view, expected2_view)
comparePlans(parsed3_view, expected3_view)
}

test("alter table: SerDe properties") {
Expand Down Expand Up @@ -376,21 +403,66 @@ class DDLCommandSuite extends PlanTest {
comparePlans(parsed2, expected2)
}

// ALTER TABLE table_name ADD [IF NOT EXISTS] PARTITION partition_spec
// [LOCATION 'location1'] partition_spec [LOCATION 'location2'] ...;
test("alter table: add partition") {
val sql =
val sql1 =
"""
|ALTER TABLE table_name ADD IF NOT EXISTS PARTITION
|(dt='2008-08-08', country='us') LOCATION 'location1' PARTITION
|(dt='2009-09-09', country='uk')
""".stripMargin
val parsed = parser.parsePlan(sql)
val expected = AlterTableAddPartition(
val sql2 = "ALTER TABLE table_name ADD PARTITION (dt='2008-08-08') LOCATION 'loc'"

val parsed1 = parser.parsePlan(sql1)
val parsed2 = parser.parsePlan(sql2)

val expected1 = AlterTableAddPartition(
TableIdentifier("table_name", None),
Seq(
(Map("dt" -> "2008-08-08", "country" -> "us"), Some("location1")),
(Map("dt" -> "2009-09-09", "country" -> "uk"), None)),
ifNotExists = true)(sql)
comparePlans(parsed, expected)
ifNotExists = true)(sql1)
val expected2 = AlterTableAddPartition(
TableIdentifier("table_name", None),
Seq((Map("dt" -> "2008-08-08"), Some("loc"))),
ifNotExists = false)(sql2)

comparePlans(parsed1, expected1)
comparePlans(parsed2, expected2)
}

// ALTER VIEW view_name ADD [IF NOT EXISTS] PARTITION partition_spec PARTITION partition_spec ...;
test("alter view: add partition") {
val sql1 =
"""
|ALTER VIEW view_name ADD IF NOT EXISTS PARTITION
|(dt='2008-08-08', country='us') PARTITION
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HiveQl allows us to use any constant on the right hand side of a partition value. Could you add a test for any other than a string?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will do it. Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Four different constant types are added. Thanks!

|(dt='2009-09-09', country='uk')
""".stripMargin
// different constant types in partitioning spec
val sql2 =
"""
|ALTER VIEW view_name ADD PARTITION
|(col1=NULL, cOL2='f', col3=5, COL4=true)
""".stripMargin

val parsed1 = parser.parsePlan(sql1)
val parsed2 = parser.parsePlan(sql2)

val expected1 = AlterTableAddPartition(
TableIdentifier("view_name", None),
Seq(
(Map("dt" -> "2008-08-08", "country" -> "us"), None),
(Map("dt" -> "2009-09-09", "country" -> "uk"), None)),
ifNotExists = true)(sql1)
val expected2 = AlterTableAddPartition(
TableIdentifier("view_name", None),
Seq((Map("col1" -> "NULL", "col2" -> "f", "col3" -> "5", "col4" -> "true"), None)),
ifNotExists = false)(sql2)

comparePlans(parsed1, expected1)
comparePlans(parsed2, expected2)
}

test("alter table: rename partition") {
Expand Down Expand Up @@ -421,36 +493,63 @@ class DDLCommandSuite extends PlanTest {
comparePlans(parsed, expected)
}

test("alter table: drop partitions") {
val sql1 =
// ALTER TABLE table_name DROP [IF EXISTS] PARTITION spec1[, PARTITION spec2, ...] [PURGE]
// ALTER VIEW table_name DROP [IF EXISTS] PARTITION spec1[, PARTITION spec2, ...]
test("alter table/view: drop partitions") {
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 =
val sql2_table =
"""
|ALTER TABLE table_name DROP PARTITION
|(dt='2008-08-08', country='us'), PARTITION (dt='2009-09-09', country='uk') PURGE
""".stripMargin
val parsed1 = parser.parsePlan(sql1)
val parsed2 = parser.parsePlan(sql2)
val sql1_view = sql1_table.replace("TABLE", "VIEW")
// Note: ALTER VIEW DROP PARTITION does not support PURGE
val sql2_view = sql2_table.replace("TABLE", "VIEW").replace("PURGE", "")

val parsed1_table = parser.parsePlan(sql1_table)
val parsed2_table = parser.parsePlan(sql2_table)
val parsed1_view = parser.parsePlan(sql1_view)
val parsed2_view = parser.parsePlan(sql2_view)

val tableIdent = TableIdentifier("table_name", None)
val expected1 = AlterTableDropPartition(
val expected1_table = AlterTableDropPartition(
tableIdent,
Seq(
Map("dt" -> "2008-08-08", "country" -> "us"),
Map("dt" -> "2009-09-09", "country" -> "uk")),
ifExists = true,
purge = false)(sql1)
val expected2 = AlterTableDropPartition(
purge = false)(sql1_table)
val expected2_table = AlterTableDropPartition(
tableIdent,
Seq(
Map("dt" -> "2008-08-08", "country" -> "us"),
Map("dt" -> "2009-09-09", "country" -> "uk")),
ifExists = false,
purge = true)(sql2)
comparePlans(parsed1, expected1)
comparePlans(parsed2, expected2)
purge = true)(sql2_table)

val expected1_view = AlterTableDropPartition(
tableIdent,
Seq(
Map("dt" -> "2008-08-08", "country" -> "us"),
Map("dt" -> "2009-09-09", "country" -> "uk")),
ifExists = true,
purge = false)(sql1_view)
val expected2_view = AlterTableDropPartition(
tableIdent,
Seq(
Map("dt" -> "2008-08-08", "country" -> "us"),
Map("dt" -> "2009-09-09", "country" -> "uk")),
ifExists = false,
purge = false)(sql2_table)

comparePlans(parsed1_table, expected1_table)
comparePlans(parsed2_table, expected2_table)
comparePlans(parsed1_view, expected1_view)
comparePlans(parsed2_view, expected2_view)
}

test("alter table: archive partition") {
Expand Down