Skip to content

Commit 2e55a9d

Browse files
committed
Merge branch 'apache-master' into metadata-only
1 parent 7d7ece0 commit 2e55a9d

5 files changed

Lines changed: 120 additions & 64 deletions

File tree

sql/core/src/main/scala/org/apache/spark/sql/execution/ExistingRDD.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,7 @@ private[sql] object DataSourceScanExec {
347347
rdd: RDD[InternalRow],
348348
relation: BaseRelation,
349349
metadata: Map[String, String] = Map.empty,
350-
metastoreTableIdentifier: Option[TableIdentifier] = None,
351-
isSupportBatch: Boolean = true): DataSourceScanExec = {
350+
metastoreTableIdentifier: Option[TableIdentifier] = None): DataSourceScanExec = {
352351
val outputPartitioning = {
353352
val bucketSpec = relation match {
354353
// TODO: this should be closer to bucket planning.
@@ -372,8 +371,7 @@ private[sql] object DataSourceScanExec {
372371

373372
relation match {
374373
case r: HadoopFsRelation
375-
if isSupportBatch &&
376-
r.fileFormat.supportBatch(r.sparkSession, StructType.fromAttributes(output)) =>
374+
if r.fileFormat.supportBatch(r.sparkSession, StructType.fromAttributes(output)) =>
377375
BatchedDataSourceScanExec(
378376
output, rdd, relation, outputPartitioning, metadata, metastoreTableIdentifier)
379377
case _ =>

sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/FileSourceStrategySuite.scala

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -338,21 +338,6 @@ class FileSourceStrategySuite extends QueryTest with SharedSQLContext with Predi
338338
assert(partitions.flatMap(fileScanRDD.preferredLocations).length == 3)
339339
}
340340
}
341-
342-
test("optimize metadataOnly") {
343-
withSQLConf("spark.sql.optimizer.metadataOnly" -> "true") {
344-
val table =
345-
createTable(
346-
files = Seq(
347-
"p1=1/file1" -> 10,
348-
"p1=2/file2" -> 10))
349-
350-
checkDataset(table.select($"p1"), Row(1), Row(2))
351-
checkDataset(table.where("p1 = 1").select($"p1"), Row(1))
352-
val df = table.where("p1 = 1 AND (p1 + c1) = 2 AND c1 = 1")
353-
assert(getPhysicalFilters(df) contains resolve(df, "c1 = 1"))
354-
}
355-
}
356341
}
357342

358343
test("SPARK-15654 do not split non-splittable files") {

sql/hive/src/main/scala/org/apache/spark/sql/hive/HiveMetastoreCatalog.scala

Lines changed: 112 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,22 @@ import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache}
2323
import org.apache.hadoop.fs.Path
2424

2525
import org.apache.spark.internal.Logging
26-
import org.apache.spark.sql.{AnalysisException, SaveMode, SparkSession}
26+
import org.apache.spark.sql.{AnalysisException, SparkSession}
2727
import org.apache.spark.sql.catalyst.{InternalRow, TableIdentifier}
2828
import org.apache.spark.sql.catalyst.catalog._
2929
import org.apache.spark.sql.catalyst.expressions._
30+
import org.apache.spark.sql.catalyst.expressions.aggregate._
3031
import org.apache.spark.sql.catalyst.plans.logical
3132
import org.apache.spark.sql.catalyst.plans.logical._
3233
import org.apache.spark.sql.catalyst.rules._
34+
import org.apache.spark.sql.execution.LogicalRDD
3335
import org.apache.spark.sql.execution.command.CreateDataSourceTableUtils._
3436
import org.apache.spark.sql.execution.command.CreateHiveTableAsSelectLogicalPlan
3537
import org.apache.spark.sql.execution.datasources.{Partition => _, _}
36-
import org.apache.spark.sql.execution.datasources.parquet.ParquetFileFormat
38+
import org.apache.spark.sql.execution.datasources.parquet.{ParquetFileFormat, ParquetOptions}
3739
import org.apache.spark.sql.hive.orc.OrcFileFormat
3840
import org.apache.spark.sql.types._
3941

40-
4142
/**
4243
* Legacy catalog for interacting with the Hive metastore.
4344
*
@@ -355,13 +356,7 @@ private[hive] class HiveMetastoreCatalog(sparkSession: SparkSession) extends Log
355356
val fileFormatClass = classOf[ParquetFileFormat]
356357

357358
val mergeSchema = sessionState.convertMetastoreParquetWithSchemaMerging
358-
val options = Map(
359-
ParquetFileFormat.MERGE_SCHEMA -> mergeSchema.toString,
360-
ParquetFileFormat.METASTORE_TABLE_NAME -> TableIdentifier(
361-
relation.tableName,
362-
Some(relation.databaseName)
363-
).unquotedString
364-
)
359+
val options = Map(ParquetOptions.MERGE_SCHEMA -> mergeSchema.toString)
365360

366361
convertToLogicalRelation(relation, options, defaultSource, fileFormatClass, "parquet")
367362
}
@@ -465,43 +460,119 @@ private[hive] class HiveMetastoreCatalog(sparkSession: SparkSession) extends Log
465460
}
466461

467462
/**
468-
* Casts input data to correct data types according to table definition before inserting into
469-
* that table.
463+
* When scanning only partition columns, get results based on metadata without scanning files.
464+
* It is used for distinct or distinct/Max/Min aggregations, example: max(partition).
470465
*/
471-
object PreInsertionCasts extends Rule[LogicalPlan] {
472-
def apply(plan: LogicalPlan): LogicalPlan = plan.transform {
473-
// Wait until children are resolved.
474-
case p: LogicalPlan if !p.childrenResolved => p
466+
object MetadataOnlyOptimizer extends Rule[LogicalPlan] {
475467

476-
case p @ InsertIntoTable(table: MetastoreRelation, _, child, _, _) =>
477-
castChildOutput(p, table, child)
468+
private def canSupportMetadataOnly(a: Aggregate): Boolean = {
469+
val aggregateExpressions = a.aggregateExpressions.flatMap { expr =>
470+
expr.collect {
471+
case agg: AggregateExpression => agg
472+
}
473+
}.distinct
474+
aggregateExpressions.forall { agg =>
475+
if (agg.isDistinct) {
476+
true
477+
} else {
478+
agg.aggregateFunction match {
479+
case max: Max => true
480+
case min: Min => true
481+
case _ => false
482+
}
483+
}
484+
}
478485
}
479486

480-
def castChildOutput(p: InsertIntoTable, table: MetastoreRelation, child: LogicalPlan)
481-
: LogicalPlan = {
482-
val childOutputDataTypes = child.output.map(_.dataType)
483-
val numDynamicPartitions = p.partition.values.count(_.isEmpty)
484-
val tableOutputDataTypes =
485-
(table.attributes ++ table.partitionKeys.takeRight(numDynamicPartitions))
486-
.take(child.output.length).map(_.dataType)
487-
488-
if (childOutputDataTypes == tableOutputDataTypes) {
489-
InsertIntoHiveTable(table, p.partition, p.child, p.overwrite, p.ifNotExists)
490-
} else if (childOutputDataTypes.size == tableOutputDataTypes.size &&
491-
childOutputDataTypes.zip(tableOutputDataTypes)
492-
.forall { case (left, right) => left.sameType(right) }) {
493-
// If both types ignoring nullability of ArrayType, MapType, StructType are the same,
494-
// use InsertIntoHiveTable instead of InsertIntoTable.
495-
InsertIntoHiveTable(table, p.partition, p.child, p.overwrite, p.ifNotExists)
496-
} else {
497-
// Only do the casting when child output data types differ from table output data types.
498-
val castedChildOutput = child.output.zip(table.output).map {
499-
case (input, output) if input.dataType != output.dataType =>
500-
Alias(Cast(input, output.dataType), input.name)()
501-
case (input, _) => input
487+
private def findRelation(plan: LogicalPlan): (Option[LogicalPlan], Seq[Expression]) = {
488+
plan match {
489+
case relation @ LogicalRelation(files: HadoopFsRelation, _, table)
490+
if files.partitionSchema.nonEmpty =>
491+
(Some(relation), Seq.empty[Expression])
492+
493+
case relation: MetastoreRelation if relation.partitionKeys.nonEmpty =>
494+
(Some(relation), Seq.empty[Expression])
495+
496+
case p @ Project(_, child) =>
497+
findRelation(child)
498+
499+
case f @ Filter(filterCondition, child) =>
500+
val (plan, conditions) = findRelation(child)
501+
(plan, conditions ++ Seq(filterCondition))
502+
503+
case SubqueryAlias(_, child) =>
504+
findRelation(child)
505+
506+
case _ => (None, Seq.empty[Expression])
507+
}
508+
}
509+
510+
private def convertToMetadataOnlyPlan(
511+
parent: LogicalPlan,
512+
project: Option[LogicalPlan],
513+
filters: Seq[Expression],
514+
relation: LogicalPlan): LogicalPlan = relation match {
515+
case l @ LogicalRelation(files: HadoopFsRelation, _, _) =>
516+
val attributeMap = l.output.map(attr => (attr.name, attr)).toMap
517+
val partitionColumns = files.partitionSchema.map { field =>
518+
attributeMap.getOrElse(field.name, throw new AnalysisException(
519+
s"Unable to resolve ${field.name} given [${l.output.map(_.name).mkString(", ")}]"))
520+
}
521+
val filterColumns = filters.flatMap(_.references)
522+
val projectSet = parent.references ++ AttributeSet(filterColumns)
523+
if (projectSet.subsetOf(AttributeSet(partitionColumns))) {
524+
val selectedPartitions = files.location.listFiles(filters)
525+
val partitionValues = selectedPartitions.map(_.values)
526+
val valuesRdd = sparkSession.sparkContext.parallelize(partitionValues, 1)
527+
val valuesPlan = LogicalRDD(partitionColumns, valuesRdd)(sparkSession)
528+
val scanPlan = project.map(_.withNewChildren(valuesPlan :: Nil)).getOrElse(valuesPlan)
529+
parent.withNewChildren(scanPlan :: Nil)
530+
} else {
531+
parent
532+
}
533+
534+
case relation: MetastoreRelation =>
535+
if (parent.references.subsetOf(AttributeSet(relation.partitionKeys))) {
536+
val partitionColumnDataTypes = relation.partitionKeys.map(_.dataType)
537+
val partitionValues = relation.getHiveQlPartitions(filters).map { p =>
538+
InternalRow.fromSeq(p.getValues.asScala.zip(partitionColumnDataTypes).map {
539+
case (rawValue, dataType) => Cast(Literal(rawValue), dataType).eval(null)
540+
})
541+
}
542+
val valuesRdd = sparkSession.sparkContext.parallelize(partitionValues, 1)
543+
val valuesPlan = LogicalRDD(relation.partitionKeys, valuesRdd)(sparkSession)
544+
val filterPlan =
545+
filters.reduceLeftOption(And).map(Filter(_, valuesPlan)).getOrElse(valuesPlan)
546+
val scanPlan = project.map(_.withNewChildren(filterPlan :: Nil)).getOrElse(filterPlan)
547+
parent.withNewChildren(scanPlan :: Nil)
548+
} else {
549+
parent
502550
}
503551

504-
p.copy(child = logical.Project(castedChildOutput, child))
552+
case _ =>
553+
parent
554+
}
555+
556+
def apply(plan: LogicalPlan): LogicalPlan = {
557+
if (!sparkSession.sessionState.conf.optimizerMetadataOnly) {
558+
return plan
559+
}
560+
plan.transform {
561+
case a @ Aggregate(_, _, child) if canSupportMetadataOnly(a) =>
562+
val (plan, filters) = findRelation(child)
563+
if (plan.isDefined) {
564+
convertToMetadataOnlyPlan(a, None, filters, plan.get)
565+
} else {
566+
a
567+
}
568+
569+
case d @ Distinct(p @ Project(_, _)) =>
570+
val (plan, filters) = findRelation(p)
571+
if (plan.isDefined) {
572+
convertToMetadataOnlyPlan(d, Some(p), filters, plan.get)
573+
} else {
574+
d
575+
}
505576
}
506577
}
507578
}

sql/hive/src/main/scala/org/apache/spark/sql/hive/HiveSessionCatalog.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ private[sql] class HiveSessionCatalog(
8787
val ParquetConversions: Rule[LogicalPlan] = metastoreCatalog.ParquetConversions
8888
val OrcConversions: Rule[LogicalPlan] = metastoreCatalog.OrcConversions
8989
val CreateTables: Rule[LogicalPlan] = metastoreCatalog.CreateTables
90-
val PreInsertionCasts: Rule[LogicalPlan] = metastoreCatalog.PreInsertionCasts
9190
val metadataOnlyOptimizer: Rule[LogicalPlan] = metastoreCatalog.MetadataOnlyOptimizer
9291

9392
override def refreshTable(name: TableIdentifier): Unit = {

sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/SQLQuerySuite.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,7 +1687,8 @@ class SQLQuerySuite extends QueryTest with SQLTestUtils with TestHiveSingleton {
16871687

16881688
test("spark-15752 metadata only optimizer") {
16891689
withSQLConf(SQLConf.OPTIMIZER_METADATA_ONLY.key -> "true") {
1690-
spark.range(0, 5, 1, 2).selectExpr("id as key", "id as value").registerTempTable("tempTable")
1690+
val df = Seq((1, 2), (3, 4)).toDF("key", "value")
1691+
df.createOrReplaceTempView("data")
16911692
sql(
16921693
"""
16931694
|CREATE TABLE srcpart_15752 (key INT, value STRING)
@@ -1697,12 +1698,13 @@ class SQLQuerySuite extends QueryTest with SQLTestUtils with TestHiveSingleton {
16971698
sql(
16981699
s"""
16991700
|INSERT OVERWRITE TABLE srcpart_15752 PARTITION (ds='$ds',hr='$hr')
1700-
|select key, value from tempTable
1701+
|select key, value from data
17011702
""".stripMargin)
17021703
}
17031704
checkAnswer(sql("select max(hr) from srcpart_15752"), Row(12))
17041705
checkAnswer(sql("select max(hr) from srcpart_15752 where hr = 11"), Row(11))
17051706
checkAnswer(sql("select max(hr) from (select hr from srcpart_15752) t"), Row(12))
1707+
checkAnswer(sql("select distinct hr from srcpart_15752"), Row(11) :: Row(12) :: Nil)
17061708
checkAnswer(sql("select distinct hr from srcpart_15752 where hr = 11"), Row(11))
17071709

17081710
sql(
@@ -1714,13 +1716,14 @@ class SQLQuerySuite extends QueryTest with SQLTestUtils with TestHiveSingleton {
17141716
sql(
17151717
s"""
17161718
|INSERT OVERWRITE TABLE srctext_15752 PARTITION (ds='$ds',hr='$hr')
1717-
|select key, value from tempTable
1719+
|select key, value from data
17181720
""".stripMargin)
17191721
}
17201722

17211723
checkAnswer(sql("select max(hr) from srctext_15752"), Row(12))
17221724
checkAnswer(sql("select max(hr) from srctext_15752 where hr = 11"), Row(11))
17231725
checkAnswer(sql("select max(hr) from (select hr from srctext_15752) t"), Row(12))
1726+
checkAnswer(sql("select distinct hr from srctext_15752"), Row(11) :: Row(12) :: Nil)
17241727
checkAnswer(sql("select distinct hr from srctext_15752 where hr = 11"), Row(11))
17251728
}
17261729
}

0 commit comments

Comments
 (0)