@@ -23,21 +23,22 @@ import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache}
2323import org .apache .hadoop .fs .Path
2424
2525import org .apache .spark .internal .Logging
26- import org .apache .spark .sql .{AnalysisException , SaveMode , SparkSession }
26+ import org .apache .spark .sql .{AnalysisException , SparkSession }
2727import org .apache .spark .sql .catalyst .{InternalRow , TableIdentifier }
2828import org .apache .spark .sql .catalyst .catalog ._
2929import org .apache .spark .sql .catalyst .expressions ._
30+ import org .apache .spark .sql .catalyst .expressions .aggregate ._
3031import org .apache .spark .sql .catalyst .plans .logical
3132import org .apache .spark .sql .catalyst .plans .logical ._
3233import org .apache .spark .sql .catalyst .rules ._
34+ import org .apache .spark .sql .execution .LogicalRDD
3335import org .apache .spark .sql .execution .command .CreateDataSourceTableUtils ._
3436import org .apache .spark .sql .execution .command .CreateHiveTableAsSelectLogicalPlan
3537import 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 }
3739import org .apache .spark .sql .hive .orc .OrcFileFormat
3840import 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 }
0 commit comments