Skip to content
This repository was archived by the owner on Jun 14, 2024. It is now read-only.

Commit 9b103c8

Browse files
author
Chungmin Lee
authored
Refactor and improve source provider API (#399)
1 parent edafcfe commit 9b103c8

13 files changed

Lines changed: 267 additions & 225 deletions

src/main/scala/com/microsoft/hyperspace/actions/CreateActionBase.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ private[actions] abstract class CreateActionBase(dataManager: IndexDataManager)
8484
Hyperspace
8585
.getContext(spark)
8686
.sourceProviderManager
87+
.getRelationMetadata(sourcePlanProperties.relations.head)
8788
.enrichIndexProperties(
88-
sourcePlanProperties.relations.head,
8989
prevIndexProperties + (IndexConstants.INDEX_LOG_VERSION -> versionId.toString)
9090
++ hasLineageProperty(spark) ++ hasParquetAsSourceFormatProperty(relation))
9191

src/main/scala/com/microsoft/hyperspace/actions/OptimizeAction.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package com.microsoft.hyperspace.actions
1818

19-
import org.apache.hadoop.conf.Configuration
2019
import org.apache.hadoop.fs.Path
2120
import org.apache.spark.sql.{SaveMode, SparkSession}
2221
import org.apache.spark.sql.execution.datasources.BucketingUtils
@@ -148,8 +147,8 @@ class OptimizeAction(
148147
properties = Hyperspace
149148
.getContext(spark)
150149
.sourceProviderManager
150+
.getRelationMetadata(previousIndexLogEntry.relations.head)
151151
.enrichIndexProperties(
152-
previousIndexLogEntry.relations.head,
153152
prevIndexProperties + (IndexConstants.INDEX_LOG_VERSION -> endId.toString))))
154153

155154
if (filesToIgnore.nonEmpty) {

src/main/scala/com/microsoft/hyperspace/actions/RefreshActionBase.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,11 @@ private[actions] abstract class RefreshActionBase(
7171
// Reconstruct a df from schema
7272
protected lazy val df = {
7373
val relations = previousIndexLogEntry.relations
74-
val latestRelation =
75-
Hyperspace.getContext(spark).sourceProviderManager.refreshRelationMetadata(relations.head)
74+
val latestRelation = Hyperspace
75+
.getContext(spark)
76+
.sourceProviderManager
77+
.getRelationMetadata(relations.head)
78+
.refresh()
7679
val dataSchema = DataType.fromJson(latestRelation.dataSchemaJson).asInstanceOf[StructType]
7780
val df = spark.read
7881
.schema(dataSchema)

src/main/scala/com/microsoft/hyperspace/actions/RefreshIncrementalAction.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ class RefreshIncrementalAction(
6060
val internalFileFormatName = Hyperspace
6161
.getContext(spark)
6262
.sourceProviderManager
63-
.internalFileFormatName(previousIndexLogEntry.relations.head)
63+
.getRelationMetadata(previousIndexLogEntry.relations.head)
64+
.internalFileFormatName()
6465

6566
// Create a df with only appended files from original list of files.
6667
val dfWithAppendedFiles = spark.read

src/main/scala/com/microsoft/hyperspace/index/rules/RuleUtils.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ object RuleUtils {
5858

5959
def signatureValid(entry: IndexLogEntry): Boolean = {
6060
entry.withCachedTag(relation.plan, IndexLogEntryTags.SIGNATURE_MATCHED) {
61-
val sourcePlanSignatures = entry.source.plan.properties.fingerprint.properties.signatures
62-
assert(sourcePlanSignatures.length == 1)
63-
val sourcePlanSignature = sourcePlanSignatures.head
61+
val sourcePlanSignature = entry.signature
6462

6563
signatureMap.getOrElseUpdate(
6664
sourcePlanSignature.provider,

src/main/scala/com/microsoft/hyperspace/index/sources/FileBasedSourceProviderManager.scala

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -43,28 +43,6 @@ class FileBasedSourceProviderManager(spark: SparkSession) {
4343
buildProviders(builderClassNames)
4444
})
4545

46-
/**
47-
* Runs refreshRelationMetadata() for each provider.
48-
*
49-
* @param relation [[Relation]] to refresh.
50-
* @return Refreshed [[Relation]].
51-
* @throws [[HyperspaceException]] if multiple providers returns [[Some]] or
52-
* if no providers return [[Some]].
53-
*/
54-
def refreshRelationMetadata(relation: Relation): Relation = {
55-
run(p => p.refreshRelationMetadata(relation))
56-
}
57-
58-
/**
59-
* Runs internalFileFormatName() for each provider.
60-
*
61-
* @param relation [[Relation]] object to read internal data files.
62-
* @return File format to read internal data files.
63-
*/
64-
def internalFileFormatName(relation: Relation): String = {
65-
run(p => p.internalFileFormatName(relation))
66-
}
67-
6846
/**
6947
* Returns true if the given logical plan is a supported relation. If all of the registered
7048
* providers return None, this returns false. (e.g, the given plan could be RDD-based relation,
@@ -91,16 +69,27 @@ class FileBasedSourceProviderManager(spark: SparkSession) {
9169
}
9270

9371
/**
94-
* Returns enriched index properties.
72+
* Returns true if the given relation metadata is a supported relation metadata. If all of the
73+
* registered providers return None, this returns false.
74+
*
75+
* @param metadata Relation metadata to check if it's supported.
76+
* @return True if the given plan is supported relation metadata.
77+
*/
78+
def isSupportedRelationMetadata(metadata: Relation): Boolean = {
79+
runWithDefault(p => p.isSupportedRelationMetadata(metadata))(false)
80+
}
81+
82+
/**
83+
* Returns the [[FileBasedRelationMetadata]] that wraps the given relation metadata.
84+
* If you are using this from an extractor, check if the relation metadata
85+
* is supported first by using [[isSupportedRelationMetadata]]. Otherwise, HyperspaceException
86+
* can be thrown if none of the registered providers supports the given relation metadata.
9587
*
96-
* @param relation Relation to retrieve necessary information.
97-
* @param properties Index properties to enrich.
98-
* @return New property entries for index creation or refresh.
88+
* @param metadata Relation metadata to wrap to [[FileBasedRelationMetadata]]
89+
* @return [[FileBasedRelationMetadata]] that wraps the given relation metadata.
9990
*/
100-
def enrichIndexProperties(
101-
relation: Relation,
102-
properties: Map[String, String]): Map[String, String] = {
103-
run(p => p.enrichIndexProperties(relation, properties))
91+
def getRelationMetadata(metadata: Relation): FileBasedRelationMetadata = {
92+
run(p => p.getRelationMetadata(metadata))
10493
}
10594

10695
/**
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (2020) The Hyperspace Project Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.microsoft.hyperspace.index.sources.default
18+
19+
import com.microsoft.hyperspace.index.Relation
20+
import com.microsoft.hyperspace.index.sources.FileBasedRelationMetadata
21+
22+
/**
23+
* Default file-based relation metadata implementation for file-based Spark built-in sources
24+
*/
25+
class DefaultFileBasedRelationMetadata(metadata: Relation) extends FileBasedRelationMetadata {
26+
27+
override def refresh(): Relation = {
28+
// No change is needed because rootPaths will be pointing to the latest source files.
29+
metadata
30+
}
31+
32+
override def internalFileFormatName(): String = {
33+
metadata.fileFormat
34+
}
35+
36+
override def enrichIndexProperties(properties: Map[String, String]): Map[String, String] = {
37+
properties
38+
}
39+
}

src/main/scala/com/microsoft/hyperspace/index/sources/default/DefaultFileBasedSource.scala

Lines changed: 19 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ package com.microsoft.hyperspace.index.sources.default
1818

1919
import java.util.Locale
2020

21-
import org.apache.spark.sql.{DataFrame, SparkSession}
21+
import org.apache.spark.sql.SparkSession
2222
import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
2323
import org.apache.spark.sql.execution.datasources._
2424
import org.apache.spark.sql.sources.DataSourceRegister
2525

2626
import com.microsoft.hyperspace.index.Relation
27-
import com.microsoft.hyperspace.index.sources.{FileBasedRelation, FileBasedSourceProvider, SourceProvider, SourceProviderBuilder}
27+
import com.microsoft.hyperspace.index.sources.{FileBasedRelation, FileBasedRelationMetadata, FileBasedSourceProvider, SourceProvider, SourceProviderBuilder}
2828
import com.microsoft.hyperspace.util.{CacheWithTransform, HyperspaceConf}
2929

3030
/**
@@ -65,37 +65,6 @@ class DefaultFileBasedSource(private val spark: SparkSession) extends FileBasedS
6565
supportedFormats.load().contains(name.toLowerCase(Locale.ROOT))
6666
}
6767

68-
/**
69-
* Given a [[Relation]], returns a new [[Relation]] that will have the latest source.
70-
*
71-
* @param relation [[Relation]] object to reconstruct [[DataFrame]] with.
72-
* @return [[Relation]] object if the given 'relation' can be processed by this provider.
73-
* Otherwise, None.
74-
*/
75-
override def refreshRelationMetadata(relation: Relation): Option[Relation] = {
76-
if (isSupportedFileFormatName(relation.fileFormat)) {
77-
// No change is needed because rootPaths will be pointing to the latest source files.
78-
Some(relation)
79-
} else {
80-
None
81-
}
82-
}
83-
84-
/**
85-
* Returns a file format name to read internal data files for a given [[Relation]].
86-
*
87-
* @param relation [[Relation]] object to read internal data files.
88-
* @return File format to read internal data files.
89-
*/
90-
override def internalFileFormatName(relation: Relation): Option[String] = {
91-
if (isSupportedFileFormatName(relation.fileFormat)) {
92-
// Same as original file format.
93-
Some(relation.fileFormat)
94-
} else {
95-
None
96-
}
97-
}
98-
9968
/**
10069
* Returns true if the given logical plan is a supported relation.
10170
*
@@ -119,28 +88,25 @@ class DefaultFileBasedSource(private val spark: SparkSession) extends FileBasedS
11988
* @param plan Logical plan to wrap to [[FileBasedRelation]]
12089
* @return [[FileBasedRelation]] that wraps the given logical plan.
12190
*/
122-
def getRelation(plan: LogicalPlan): Option[FileBasedRelation] = plan match {
123-
case l @ LogicalRelation(
124-
HadoopFsRelation(_: PartitioningAwareFileIndex, _, _, _, fileFormat, _),
125-
_,
126-
_,
127-
_) if isSupportedFileFormat(fileFormat) =>
128-
Some(new DefaultFileBasedRelation(spark, l))
129-
case _ => None
91+
def getRelation(plan: LogicalPlan): Option[FileBasedRelation] = {
92+
if (isSupportedRelation(plan).contains(true)) {
93+
Some(new DefaultFileBasedRelation(spark, plan.asInstanceOf[LogicalRelation]))
94+
} else {
95+
None
96+
}
13097
}
13198

132-
/**
133-
* Returns enriched index properties.
134-
*
135-
* @param relation Relation to retrieve necessary information.
136-
* @param properties Index properties to enrich.
137-
* @return Updated index properties for index creation or refresh.
138-
*/
139-
override def enrichIndexProperties(
140-
relation: Relation,
141-
properties: Map[String, String]): Option[Map[String, String]] = {
142-
if (isSupportedFileFormatName(relation.fileFormat)) {
143-
Some(properties)
99+
override def isSupportedRelationMetadata(metadata: Relation): Option[Boolean] = {
100+
if (isSupportedFileFormatName(metadata.fileFormat)) {
101+
Some(true)
102+
} else {
103+
None
104+
}
105+
}
106+
107+
override def getRelationMetadata(metadata: Relation): Option[FileBasedRelationMetadata] = {
108+
if (isSupportedRelationMetadata(metadata).contains(true)) {
109+
Some(new DefaultFileBasedRelationMetadata(metadata))
144110
} else {
145111
None
146112
}

src/main/scala/com/microsoft/hyperspace/index/sources/delta/DeltaLakeFileBasedSource.scala

Lines changed: 19 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616

1717
package com.microsoft.hyperspace.index.sources.delta
1818

19-
import org.apache.spark.sql.{DataFrame, SparkSession}
19+
import org.apache.spark.sql.SparkSession
2020
import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
2121
import org.apache.spark.sql.delta.files.TahoeLogFileIndex
2222
import org.apache.spark.sql.execution.datasources.{HadoopFsRelation, LogicalRelation}
2323

2424
import com.microsoft.hyperspace.index.{IndexConstants, Relation}
25-
import com.microsoft.hyperspace.index.sources.{FileBasedRelation, FileBasedSourceProvider, SourceProvider, SourceProviderBuilder}
25+
import com.microsoft.hyperspace.index.sources.{FileBasedRelation, FileBasedRelationMetadata, FileBasedSourceProvider, SourceProvider, SourceProviderBuilder}
2626

2727
object DeltaLakeConstants {
2828
val DELTA_FORMAT_STR = "delta"
@@ -39,35 +39,6 @@ object DeltaLakeConstants {
3939
*/
4040
class DeltaLakeFileBasedSource(private val spark: SparkSession) extends FileBasedSourceProvider {
4141

42-
/**
43-
* Given a [[Relation]], returns a new [[Relation]] that will have the latest source.
44-
*
45-
* @param relation [[Relation]] object to reconstruct [[DataFrame]] with.
46-
* @return [[Relation]] object if the given 'relation' can be processed by this provider.
47-
* Otherwise, None.
48-
*/
49-
override def refreshRelationMetadata(relation: Relation): Option[Relation] = {
50-
if (relation.fileFormat.equals(DeltaLakeConstants.DELTA_FORMAT_STR)) {
51-
Some(relation.copy(options = relation.options - "versionAsOf" - "timestampAsOf"))
52-
} else {
53-
None
54-
}
55-
}
56-
57-
/**
58-
* Returns a file format name to read internal data files for a given [[Relation]].
59-
*
60-
* @param relation [[Relation]] object to read internal data files.
61-
* @return File format to read internal data files.
62-
*/
63-
override def internalFileFormatName(relation: Relation): Option[String] = {
64-
if (relation.fileFormat.equals(DeltaLakeConstants.DELTA_FORMAT_STR)) {
65-
Some("parquet")
66-
} else {
67-
None
68-
}
69-
}
70-
7142
/**
7243
* Returns true if the given logical plan is a relation for Delta Lake.
7344
*
@@ -87,38 +58,27 @@ class DeltaLakeFileBasedSource(private val spark: SparkSession) extends FileBase
8758
* @param plan Logical plan to wrap to [[FileBasedRelation]]
8859
* @return [[FileBasedRelation]] that wraps the given logical plan.
8960
*/
90-
def getRelation(plan: LogicalPlan): Option[FileBasedRelation] = plan match {
91-
case l @ LogicalRelation(HadoopFsRelation(_: TahoeLogFileIndex, _, _, _, _, _), _, _, _) =>
92-
Some(new DeltaLakeRelation(spark, l))
93-
case _ => None
61+
def getRelation(plan: LogicalPlan): Option[FileBasedRelation] = {
62+
if (isSupportedRelation(plan).contains(true)) {
63+
Some(new DeltaLakeRelation(spark, plan.asInstanceOf[LogicalRelation]))
64+
} else {
65+
None
66+
}
9467
}
9568

96-
/**
97-
* Returns enriched index properties.
98-
*
99-
* Delta Lake source provider adds:
100-
* 1) DELTA_VERSION_HISTORY_PROPERTY logs the history of INDEX_VERSION:DELTA_TABLE_VERSION
101-
* values for each index creation & refresh.
102-
*
103-
* @param relation Relation to retrieve necessary information.
104-
* @param properties Index properties to enrich.
105-
* @return Update index properties for index creation or refresh.
106-
*/
107-
override def enrichIndexProperties(
108-
relation: Relation,
109-
properties: Map[String, String]): Option[Map[String, String]] = {
110-
if (!relation.fileFormat.equals(DeltaLakeConstants.DELTA_FORMAT_STR)) {
69+
override def isSupportedRelationMetadata(metadata: Relation): Option[Boolean] = {
70+
if (metadata.fileFormat.equals(DeltaLakeConstants.DELTA_FORMAT_STR)) {
71+
Some(true)
72+
} else {
11173
None
74+
}
75+
}
76+
77+
override def getRelationMetadata(metadata: Relation): Option[FileBasedRelationMetadata] = {
78+
if (isSupportedRelationMetadata(metadata).contains(true)) {
79+
Some(new DeltaLakeRelationMetadata(metadata))
11280
} else {
113-
val indexVersion = properties(IndexConstants.INDEX_LOG_VERSION)
114-
val deltaVerHistory = relation.options.get("versionAsOf").map { deltaVersion =>
115-
val newVersionMapping = s"$indexVersion:$deltaVersion"
116-
DeltaLakeConstants.DELTA_VERSION_HISTORY_PROPERTY ->
117-
properties.get(DeltaLakeConstants.DELTA_VERSION_HISTORY_PROPERTY).map { prop =>
118-
s"$prop,$newVersionMapping"
119-
}.getOrElse(newVersionMapping)
120-
}
121-
Some(properties ++ deltaVerHistory)
81+
None
12282
}
12383
}
12484
}

0 commit comments

Comments
 (0)