[HUDI-4170] Make user can use hoodie.datasource.read.paths to read necessary files (#5722)
* Rebase codes * Move listFileSlices to HoodieBaseRelation * Fix review * Fix style * Fix bug
This commit is contained in:
@@ -68,11 +68,9 @@ class DefaultSource extends RelationProvider
|
||||
override def createRelation(sqlContext: SQLContext,
|
||||
optParams: Map[String, String],
|
||||
schema: StructType): BaseRelation = {
|
||||
// Add default options for unspecified read options keys.
|
||||
val parameters = DataSourceOptionsHelper.parametersWithReadDefaults(optParams)
|
||||
val path = optParams.get("path")
|
||||
val readPathsStr = optParams.get(DataSourceReadOptions.READ_PATHS.key)
|
||||
|
||||
val path = parameters.get("path")
|
||||
val readPathsStr = parameters.get(DataSourceReadOptions.READ_PATHS.key)
|
||||
if (path.isEmpty && readPathsStr.isEmpty) {
|
||||
throw new HoodieException(s"'path' or '$READ_PATHS' or both must be specified.")
|
||||
}
|
||||
@@ -87,6 +85,16 @@ class DefaultSource extends RelationProvider
|
||||
} else {
|
||||
Seq.empty
|
||||
}
|
||||
|
||||
// Add default options for unspecified read options keys.
|
||||
val parameters = (if (globPaths.nonEmpty) {
|
||||
Map(
|
||||
"glob.paths" -> globPaths.mkString(",")
|
||||
)
|
||||
} else {
|
||||
Map()
|
||||
}) ++ DataSourceOptionsHelper.parametersWithReadDefaults(optParams)
|
||||
|
||||
// Get the table base path
|
||||
val tablePath = if (globPaths.nonEmpty) {
|
||||
DataSourceUtils.getTablePath(fs, globPaths.toArray)
|
||||
|
||||
@@ -26,9 +26,10 @@ import org.apache.hadoop.mapred.JobConf
|
||||
import org.apache.hudi.HoodieBaseRelation.{convertToAvroSchema, createHFileReader, generateUnsafeProjection, getPartitionPath}
|
||||
import org.apache.hudi.HoodieConversionUtils.toScalaOption
|
||||
import org.apache.hudi.avro.HoodieAvroUtils
|
||||
import org.apache.hudi.common.config.{HoodieMetadataConfig, SerializableConfiguration}
|
||||
import org.apache.hudi.common.config.HoodieMetadataConfig
|
||||
import org.apache.hudi.common.fs.FSUtils
|
||||
import org.apache.hudi.common.model.{HoodieFileFormat, HoodieRecord}
|
||||
import org.apache.hudi.common.fs.FSUtils.getRelativePartitionPath
|
||||
import org.apache.hudi.common.model.{FileSlice, HoodieFileFormat, HoodieRecord}
|
||||
import org.apache.hudi.common.table.timeline.{HoodieInstant, HoodieTimeline}
|
||||
import org.apache.hudi.common.table.view.HoodieTableFileSystemView
|
||||
import org.apache.hudi.common.table.{HoodieTableConfig, HoodieTableMetaClient, TableSchemaResolver}
|
||||
@@ -44,7 +45,7 @@ import org.apache.spark.rdd.RDD
|
||||
import org.apache.spark.sql.catalyst.InternalRow
|
||||
import org.apache.spark.sql.catalyst.expressions.{Expression, SubqueryExpression}
|
||||
import org.apache.spark.sql.execution.FileRelation
|
||||
import org.apache.spark.sql.execution.datasources.{FileStatusCache, PartitionedFile, PartitioningUtils}
|
||||
import org.apache.spark.sql.execution.datasources.{FileStatusCache, PartitionDirectory, PartitionedFile, PartitioningUtils}
|
||||
import org.apache.spark.sql.hudi.HoodieSqlCommonUtils
|
||||
import org.apache.spark.sql.sources.{BaseRelation, Filter, PrunedFilteredScan}
|
||||
import org.apache.spark.sql.types.{StringType, StructField, StructType}
|
||||
@@ -222,7 +223,7 @@ abstract class HoodieBaseRelation(val sqlContext: SQLContext,
|
||||
toScalaOption(timeline.lastInstant())
|
||||
|
||||
protected def queryTimestamp: Option[String] =
|
||||
specifiedQueryTimestamp.orElse(toScalaOption(timeline.lastInstant()).map(_.getTimestamp))
|
||||
specifiedQueryTimestamp.orElse(latestInstant.map(_.getTimestamp))
|
||||
|
||||
/**
|
||||
* Returns true in case table supports Schema on Read (Schema Evolution)
|
||||
@@ -340,20 +341,49 @@ abstract class HoodieBaseRelation(val sqlContext: SQLContext,
|
||||
*/
|
||||
protected def collectFileSplits(partitionFilters: Seq[Expression], dataFilters: Seq[Expression]): Seq[FileSplit]
|
||||
|
||||
protected def listLatestBaseFiles(globbedPaths: Seq[Path], partitionFilters: Seq[Expression], dataFilters: Seq[Expression]): Map[Path, Seq[FileStatus]] = {
|
||||
val partitionDirs = if (globbedPaths.isEmpty) {
|
||||
/**
|
||||
* Get all PartitionDirectories based on globPaths if specified, otherwise use the table path.
|
||||
* Will perform pruning if necessary
|
||||
*/
|
||||
private def listPartitionDirectories(globPaths: Seq[Path], partitionFilters: Seq[Expression], dataFilters: Seq[Expression]): Seq[PartitionDirectory] = {
|
||||
if (globPaths.isEmpty) {
|
||||
fileIndex.listFiles(partitionFilters, dataFilters)
|
||||
} else {
|
||||
val inMemoryFileIndex = HoodieInMemoryFileIndex.create(sparkSession, globbedPaths)
|
||||
val inMemoryFileIndex = HoodieInMemoryFileIndex.create(sparkSession, globPaths)
|
||||
inMemoryFileIndex.listFiles(partitionFilters, dataFilters)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all latest base files with partition paths, if globPaths is empty, will listing files
|
||||
* under the table path.
|
||||
*/
|
||||
protected def listLatestBaseFiles(globPaths: Seq[Path], partitionFilters: Seq[Expression], dataFilters: Seq[Expression]): Map[Path, Seq[FileStatus]] = {
|
||||
val partitionDirs = listPartitionDirectories(globPaths, partitionFilters, dataFilters)
|
||||
val fsView = new HoodieTableFileSystemView(metaClient, timeline, partitionDirs.flatMap(_.files).toArray)
|
||||
|
||||
val latestBaseFiles = fsView.getLatestBaseFiles.iterator().asScala.toList.map(_.getFileStatus)
|
||||
|
||||
latestBaseFiles.groupBy(getPartitionPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all fileSlices(contains base files and log files if exist) from globPaths if not empty,
|
||||
* otherwise will use the table path to do the listing.
|
||||
*/
|
||||
protected def listLatestFileSlices(globPaths: Seq[Path], partitionFilters: Seq[Expression], dataFilters: Seq[Expression]): Seq[FileSlice] = {
|
||||
latestInstant.map { _ =>
|
||||
val partitionDirs = listPartitionDirectories(globPaths, partitionFilters, dataFilters)
|
||||
val fsView = new HoodieTableFileSystemView(metaClient, timeline, partitionDirs.flatMap(_.files).toArray)
|
||||
|
||||
val queryTimestamp = this.queryTimestamp.get
|
||||
fsView.getPartitionPaths.asScala.flatMap { partitionPath =>
|
||||
val relativePath = getRelativePartitionPath(new Path(basePath), partitionPath)
|
||||
fsView.getLatestMergedFileSlicesBeforeOrOn(relativePath, queryTimestamp).iterator().asScala.toSeq
|
||||
}
|
||||
}.getOrElse(Seq())
|
||||
}
|
||||
|
||||
protected def convertToExpressions(filters: Array[Filter]): Array[Expression] = {
|
||||
val catalystExpressions = HoodieSparkUtils.convertToCatalystExpressions(filters, tableStructSchema)
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ case class HoodieFileIndex(spark: SparkSession,
|
||||
metaClient = metaClient,
|
||||
schemaSpec = schemaSpec,
|
||||
configProperties = getConfigProperties(spark, options),
|
||||
queryPaths = Seq(HoodieFileIndex.getQueryPath(options)),
|
||||
queryPaths = HoodieFileIndex.getQueryPaths(options),
|
||||
specifiedQueryInstant = options.get(DataSourceReadOptions.TIME_TRAVEL_AS_OF_INSTANT.key).map(HoodieSqlCommonUtils.formatQueryInstant),
|
||||
fileStatusCache = fileStatusCache
|
||||
)
|
||||
@@ -341,10 +341,15 @@ object HoodieFileIndex extends Logging {
|
||||
}
|
||||
}
|
||||
|
||||
private def getQueryPath(options: Map[String, String]) = {
|
||||
new Path(options.get("path") match {
|
||||
case Some(p) => p
|
||||
case None => throw new IllegalArgumentException("'path' option required")
|
||||
})
|
||||
private def getQueryPaths(options: Map[String, String]): Seq[Path] = {
|
||||
options.get("path") match {
|
||||
case Some(p) => Seq(new Path(p))
|
||||
case None =>
|
||||
options.getOrElse("glob.paths",
|
||||
throw new IllegalArgumentException("'path' or 'glob paths' option required"))
|
||||
.split(",")
|
||||
.map(new Path(_))
|
||||
.toSeq
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,24 +104,8 @@ class MergeOnReadSnapshotRelation(sqlContext: SQLContext,
|
||||
val fileSlices = fileIndex.listFileSlices(convertedPartitionFilters)
|
||||
buildSplits(fileSlices.values.flatten.toSeq)
|
||||
} else {
|
||||
val inMemoryFileIndex = HoodieInMemoryFileIndex.create(sparkSession, globPaths)
|
||||
val partitionDirs = inMemoryFileIndex.listFiles(partitionFilters, dataFilters)
|
||||
|
||||
val fsView = new HoodieTableFileSystemView(metaClient, timeline, partitionDirs.flatMap(_.files).toArray)
|
||||
val partitionPaths = fsView.getPartitionPaths.asScala
|
||||
|
||||
if (partitionPaths.isEmpty || latestInstant.isEmpty) {
|
||||
// If this an empty table OR it has no completed commits yet, return
|
||||
List.empty[HoodieMergeOnReadFileSplit]
|
||||
} else {
|
||||
val queryTimestamp = this.queryTimestamp.get
|
||||
|
||||
val fileSlices = partitionPaths.flatMap { partitionPath =>
|
||||
val relativePath = getRelativePartitionPath(new Path(basePath), partitionPath)
|
||||
fsView.getLatestMergedFileSlicesBeforeOrOn(relativePath, queryTimestamp).iterator().asScala.toSeq
|
||||
}
|
||||
buildSplits(fileSlices)
|
||||
}
|
||||
val fileSlices = listLatestFileSlices(globPaths, partitionFilters, dataFilters)
|
||||
buildSplits(fileSlices)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user