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

Commit 336728d

Browse files
authored
Merge pull request #1 from microsoft/master
Sync
2 parents c900cf5 + b8a44b4 commit 336728d

40 files changed

Lines changed: 745 additions & 607 deletions

azure-pipelines.yml

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,24 @@ jobs:
1717
versionSpec: '8'
1818
jdkArchitectureOption: 'x64'
1919
jdkSourceOption: 'PreInstalled'
20-
- script: sbt ++2.11.12 clean
20+
# Use sbt 1.4.9. The default sbt launcher in ubuntu-18.04 20210405 image is
21+
# 1.5.0, but the version has an issue to compile with 0.13.18.
22+
# See: https://github.com/sbt/sbt/issues/6447
23+
- script: wget -O /tmp/sbt.tgz "https://github.com/sbt/sbt/releases/download/v1.4.9/sbt-1.4.9.tgz"
24+
displayName: 'Download sbt 1.4.9'
25+
- script: tar zxf /tmp/sbt.tgz -C /tmp/
26+
displayName: 'Extract sbt'
27+
- script: /tmp/sbt//bin/sbt ++2.11.12 clean
2128
displayName: 'Running $sbt clean'
22-
- script: sbt ++2.11.12 update
29+
- script: /tmp/sbt/bin/sbt ++2.11.12 update
2330
displayName: 'Running $sbt update'
24-
- script: sbt ++2.11.12 compile
31+
- script: /tmp/sbt/bin/sbt ++2.11.12 compile
2532
displayName: 'Running $sbt compile'
26-
- script: sbt ++2.11.12 test
33+
- script: /tmp/sbt/bin/sbt ++2.11.12 test
2734
displayName: 'Running $sbt test'
2835
# If not a pull request, publish artifacts.
2936
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
30-
- script: sbt ++2.11.12 package
37+
- script: /tmp/sbt/bin/sbt ++2.11.12 package
3138
displayName: 'Running $sbt package'
3239
- task: CopyFiles@2
3340
displayName: 'Copy hyperspace-core JAR'
@@ -46,17 +53,21 @@ jobs:
4653
pool:
4754
vmImage: 'ubuntu-18.04'
4855
steps:
49-
- script: sbt ++2.12.8 clean
56+
- script: wget -O /tmp/sbt.tgz "https://github.com/sbt/sbt/releases/download/v1.4.9/sbt-1.4.9.tgz"
57+
displayName: 'Download sbt 1.4.9'
58+
- script: tar zxf /tmp/sbt.tgz -C /tmp/
59+
displayName: 'Extract sbt'
60+
- script: /tmp/sbt/bin/sbt ++2.12.8 clean
5061
displayName: 'Running $sbt clean'
51-
- script: sbt ++2.12.8 update
62+
- script: /tmp/sbt/bin/sbt ++2.12.8 update
5263
displayName: 'Running $sbt update'
53-
- script: sbt ++2.12.8 compile
64+
- script: /tmp/sbt/bin/sbt ++2.12.8 compile
5465
displayName: 'Running $sbt compile'
55-
- script: sbt ++2.12.8 test
66+
- script: /tmp/sbt/bin/sbt ++2.12.8 test
5667
displayName: 'Running $sbt test'
5768
# If not a pull request, publish artifacts.
5869
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
59-
- script: sbt ++2.12.8 package
70+
- script: /tmp/sbt/bin/sbt ++2.12.8 package
6071
displayName: 'Running $sbt package'
6172
- task: CopyFiles@2
6273
displayName: 'Copy hyperspace-core JAR'
@@ -86,11 +97,15 @@ jobs:
8697
versionSpec: '8'
8798
jdkArchitectureOption: 'x64'
8899
jdkSourceOption: 'PreInstalled'
89-
- script: sbt ++2.11.12 clean
100+
- script: wget -O /tmp/sbt.tgz "https://github.com/sbt/sbt/releases/download/v1.4.9/sbt-1.4.9.tgz"
101+
displayName: 'Download sbt 1.4.9'
102+
- script: tar zxf /tmp/sbt.tgz -C /tmp/
103+
displayName: 'Extract sbt'
104+
- script: /tmp/sbt/bin/sbt ++2.11.12 clean
90105
displayName: 'Running $sbt clean'
91-
- script: sbt ++2.11.12 update
106+
- script: /tmp/sbt/bin/sbt ++2.11.12 update
92107
displayName: 'Running $sbt update'
93-
- script: sbt ++2.11.12 compile
108+
- script: /tmp/sbt/bin/sbt ++2.11.12 compile
94109
displayName: 'Running $sbt compile'
95110
- task: Bash@3
96111
inputs:

scalastyle-config.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ This file is divided into 3 sections:
9292
*/
9393
9494
\E)?\Q/*
95-
* Copyright (2020) The Hyperspace Project Authors.
95+
* Copyright (\E(?:2020|2021)\Q) The Hyperspace Project Authors.
9696
*
9797
* Licensed under the Apache License, Version 2.0 (the "License");
9898
* you may not use this file except in compliance with the License.

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

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import com.microsoft.hyperspace.{Hyperspace, HyperspaceException}
2525
import com.microsoft.hyperspace.index._
2626
import com.microsoft.hyperspace.index.DataFrameWriterExtensions.Bucketizer
2727
import com.microsoft.hyperspace.index.sources.FileBasedRelation
28-
import com.microsoft.hyperspace.util.{HyperspaceConf, PathUtils, ResolverUtils, SchemaUtils}
28+
import com.microsoft.hyperspace.util.{HyperspaceConf, PathUtils, ResolverUtils}
29+
import com.microsoft.hyperspace.util.ResolverUtils.ResolvedColumn
2930

3031
/**
3132
* CreateActionBase provides functionality to write dataframe as covering index.
@@ -83,17 +84,19 @@ private[actions] abstract class CreateActionBase(dataManager: IndexDataManager)
8384
Hyperspace
8485
.getContext(spark)
8586
.sourceProviderManager
87+
.getRelationMetadata(sourcePlanProperties.relations.head)
8688
.enrichIndexProperties(
87-
sourcePlanProperties.relations.head,
8889
prevIndexProperties + (IndexConstants.INDEX_LOG_VERSION -> versionId.toString)
8990
++ hasLineageProperty(spark) ++ hasParquetAsSourceFormatProperty(relation))
9091

91-
IndexLogEntry(
92+
IndexLogEntry.create(
9293
indexConfig.indexName,
9394
CoveringIndex(
9495
CoveringIndex.Properties(
9596
CoveringIndex.Properties
96-
.Columns(resolvedIndexedColumns, resolvedIncludedColumns),
97+
.Columns(
98+
resolvedIndexedColumns.map(_.normalizedName),
99+
resolvedIncludedColumns.map(_.normalizedName)),
97100
IndexLogEntry.schemaString(indexDataFrame.schema),
98101
numBuckets,
99102
coveringIndexProperties)),
@@ -113,10 +116,8 @@ private[actions] abstract class CreateActionBase(dataManager: IndexDataManager)
113116

114117
// Run job
115118
val repartitionedIndexDataFrame = {
116-
// For nested fields, resolvedIndexedColumns will have flattened names with `.` (dots),
117-
// thus they need to be enclosed in backticks to access them as top-level columns.
118-
// Note that backticking the non-nested columns is a no-op.
119-
indexDataFrame.repartition(numBuckets, resolvedIndexedColumns.map(c => col(s"`$c`")): _*)
119+
// We are repartitioning with normalized columns (e.g., flattened nested column).
120+
indexDataFrame.repartition(numBuckets, resolvedIndexedColumns.map(_.toNormalizedColumn): _*)
120121
}
121122

122123
// Save the index with the number of buckets specified.
@@ -125,7 +126,7 @@ private[actions] abstract class CreateActionBase(dataManager: IndexDataManager)
125126
repartitionedIndexDataFrame,
126127
indexDataPath.toString,
127128
numBuckets,
128-
resolvedIndexedColumns,
129+
resolvedIndexedColumns.map(_.normalizedName),
129130
SaveMode.Overwrite)
130131
}
131132

@@ -159,7 +160,7 @@ private[actions] abstract class CreateActionBase(dataManager: IndexDataManager)
159160

160161
private def resolveConfig(
161162
df: DataFrame,
162-
indexConfig: IndexConfig): (Seq[(String, Boolean)], Seq[(String, Boolean)]) = {
163+
indexConfig: IndexConfig): (Seq[ResolvedColumn], Seq[ResolvedColumn]) = {
163164
val spark = df.sparkSession
164165
val plan = df.queryExecution.analyzed
165166
val indexedColumns = indexConfig.indexedColumns
@@ -171,7 +172,7 @@ private[actions] abstract class CreateActionBase(dataManager: IndexDataManager)
171172
case (Some(indexed), Some(included)) => (indexed, included)
172173
case _ =>
173174
val unresolvedColumns = (indexedColumns ++ includedColumns)
174-
.map(c => (c, ResolverUtils.resolve(spark, Seq(c), plan).map(_.map(_._1))))
175+
.map(c => (c, ResolverUtils.resolve(spark, Seq(c), plan).map(_.map(_.name))))
175176
.collect { case (c, r) if r.isEmpty => c }
176177
throw HyperspaceException(
177178
s"Columns '${unresolvedColumns.mkString(",")}' could not be resolved " +
@@ -182,14 +183,9 @@ private[actions] abstract class CreateActionBase(dataManager: IndexDataManager)
182183
private def prepareIndexDataFrame(
183184
spark: SparkSession,
184185
df: DataFrame,
185-
indexConfig: IndexConfig): (DataFrame, Seq[String], Seq[String]) = {
186+
indexConfig: IndexConfig): (DataFrame, Seq[ResolvedColumn], Seq[ResolvedColumn]) = {
186187
val (resolvedIndexedColumns, resolvedIncludedColumns) = resolveConfig(df, indexConfig)
187-
val columnsFromIndexConfig =
188-
resolvedIndexedColumns.map(_._1) ++ resolvedIncludedColumns.map(_._1)
189-
190-
val prefixedIndexedColumns = SchemaUtils.prefixNestedFieldNames(resolvedIndexedColumns)
191-
val prefixedIncludedColumns = SchemaUtils.prefixNestedFieldNames(resolvedIncludedColumns)
192-
val prefixedColumnsFromIndexConfig = prefixedIndexedColumns ++ prefixedIncludedColumns
188+
val projectColumns = (resolvedIndexedColumns ++ resolvedIncludedColumns).map(_.toColumn)
193189

194190
val indexDF = if (hasLineage(spark)) {
195191
val relation = getRelation(spark, df)
@@ -198,10 +194,12 @@ private[actions] abstract class CreateActionBase(dataManager: IndexDataManager)
198194
// 1. DATA_FILE_ID_COLUMN column contains source data file id for each index record.
199195
// 2. If source data is partitioned, all partitioning key(s) are added to index schema
200196
// as columns if they are not already part of the schema.
201-
val partitionColumns = relation.partitionSchema.map(_.name)
197+
val partitionColumnNames = relation.partitionSchema.map(_.name)
198+
val resolvedColumnNames = (resolvedIndexedColumns ++ resolvedIncludedColumns).map(_.name)
202199
val missingPartitionColumns =
203-
partitionColumns.filter(ResolverUtils.resolve(spark, _, columnsFromIndexConfig).isEmpty)
204-
val allIndexColumns = columnsFromIndexConfig ++ missingPartitionColumns
200+
partitionColumnNames
201+
.filter(ResolverUtils.resolve(spark, _, resolvedColumnNames).isEmpty)
202+
.map(col(_))
205203

206204
// File id value in DATA_FILE_ID_COLUMN column (lineage column) is stored as a
207205
// Long data type value. Each source data file has a unique file id, assigned by
@@ -218,25 +216,15 @@ private[actions] abstract class CreateActionBase(dataManager: IndexDataManager)
218216
val dataPathColumn = "_data_path"
219217
val lineagePairs = relation.lineagePairs(fileIdTracker)
220218
val lineageDF = lineagePairs.toDF(dataPathColumn, IndexConstants.DATA_FILE_NAME_ID)
221-
val prefixedAllIndexColumns = prefixedColumnsFromIndexConfig ++ missingPartitionColumns
222219

223220
df.withColumn(dataPathColumn, input_file_name())
224221
.join(lineageDF.hint("broadcast"), dataPathColumn)
225-
.select(prepareColumns(allIndexColumns, prefixedAllIndexColumns) :+
226-
col(IndexConstants.DATA_FILE_NAME_ID): _*)
222+
.select(
223+
projectColumns ++ missingPartitionColumns :+ col(IndexConstants.DATA_FILE_NAME_ID): _*)
227224
} else {
228-
df.select(prepareColumns(columnsFromIndexConfig, prefixedColumnsFromIndexConfig): _*)
225+
df.select(projectColumns: _*)
229226
}
230227

231-
(indexDF, prefixedIndexedColumns, prefixedIncludedColumns)
232-
}
233-
234-
private def prepareColumns(
235-
originalColumns: Seq[String],
236-
prefixedColumns: Seq[String]): Seq[Column] = {
237-
originalColumns.zip(prefixedColumns).map {
238-
case (original, prefixed) =>
239-
col(original).as(prefixed)
240-
}
228+
(indexDF, resolvedIndexedColumns, resolvedIncludedColumns)
241229
}
242230
}

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: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import org.apache.spark.sql.types.{DataType, StructType}
2222
import com.microsoft.hyperspace.{Hyperspace, HyperspaceException}
2323
import com.microsoft.hyperspace.actions.Constants.States.{ACTIVE, REFRESHING}
2424
import com.microsoft.hyperspace.index._
25-
import com.microsoft.hyperspace.util.SchemaUtils
25+
import com.microsoft.hyperspace.util.ResolverUtils.ResolvedColumn
2626

2727
/**
2828
* Base abstract class containing common code for different types of index refresh actions.
@@ -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)
@@ -95,8 +98,8 @@ private[actions] abstract class RefreshActionBase(
9598
previousIndexLogEntry.name,
9699
// As indexed & included columns in previousLogEntry are resolved & prefixed names,
97100
// need to remove the prefix to resolve with the dataframe for refresh.
98-
SchemaUtils.removePrefixNestedFieldNames(ddColumns.indexed).map(_._1),
99-
SchemaUtils.removePrefixNestedFieldNames(ddColumns.included).map(_._1))
101+
ddColumns.indexed.map(ResolvedColumn(_).name),
102+
ddColumns.included.map(ResolvedColumn(_).name))
100103
}
101104

102105
final override val transientState: String = REFRESHING

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/IndexCollectionManager.scala

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@ class IndexCollectionManager(
138138
}
139139
}
140140

141+
override def getIndex(indexName: String, logVersion: Int): Option[IndexLogEntry] = {
142+
withLogManager(indexName) { logManager =>
143+
logManager.getLog(logVersion).map(_.asInstanceOf[IndexLogEntry])
144+
}
145+
}
146+
147+
override def getIndexVersions(indexName: String, states: Seq[String]): Seq[Int] = {
148+
withLogManager(indexName) { logManager =>
149+
logManager.getIndexVersions(states)
150+
}
151+
}
152+
141153
private def indexLogManagers: Seq[IndexLogManager] = {
142154
val hadoopConf = spark.sessionState.newHadoopConf()
143155
val rootPath = PathResolver(conf, hadoopConf).systemPath
@@ -162,12 +174,6 @@ class IndexCollectionManager(
162174
}
163175
}
164176

165-
override def getIndex(indexName: String, logVersion: Int): Option[IndexLogEntry] = {
166-
withLogManager(indexName) { logManager =>
167-
logManager.getLog(logVersion).map(_.asInstanceOf[IndexLogEntry])
168-
}
169-
}
170-
171177
private def withLogManager[T](indexName: String)(f: IndexLogManager => T): T = {
172178
getLogManager(indexName) match {
173179
case Some(logManager) => f(logManager)

src/main/scala/com/microsoft/hyperspace/index/IndexConstants.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ object IndexConstants {
102102
private[hyperspace] val LINEAGE_PROPERTY = "lineage"
103103
// Indicate whether the source file format is parquet.
104104
private[hyperspace] val HAS_PARQUET_AS_SOURCE_FORMAT_PROPERTY = "hasParquetAsSourceFormat"
105+
// Indicate Hyperspace version.
106+
private[hyperspace] val HYPERSPACE_VERSION_PROPERTY: String = "hyperspaceVersion"
105107
// Indicate index log version.
106108
private[hyperspace] val INDEX_LOG_VERSION = "indexLogVersion"
107109

src/main/scala/com/microsoft/hyperspace/index/IndexLogEntry.scala

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ import org.apache.spark.sql.catalyst.catalog.BucketSpec
2929
import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
3030
import org.apache.spark.sql.types.{DataType, StructType}
3131

32-
import com.microsoft.hyperspace.HyperspaceException
32+
import com.microsoft.hyperspace.{BuildInfo, HyperspaceException}
3333
import com.microsoft.hyperspace.actions.Constants
34-
import com.microsoft.hyperspace.util.{PathUtils, SchemaUtils}
34+
import com.microsoft.hyperspace.util.PathUtils
3535

3636
// IndexLogEntry-specific fingerprint to be temporarily used where fingerprint is not defined.
3737
case class NoOpFingerprint() {
@@ -430,7 +430,12 @@ object SparkPlan {
430430
// IndexLogEntry-specific Source that uses SparkPlan as a plan.
431431
case class Source(plan: SparkPlan)
432432

433-
// IndexLogEntry that captures index-related information.
433+
/**
434+
* IndexLogEntry that captures index-related information.
435+
* Don't use this method to create a new IndexLogEntry, unless you specify all hyperspace project
436+
* default properties.
437+
* Refer the create method of IndexLogEntry Object for further details.
438+
*/
434439
case class IndexLogEntry(
435440
name: String,
436441
derivedDataset: CoveringIndex,
@@ -518,6 +523,7 @@ case class IndexLogEntry(
518523
numBuckets.equals(that.numBuckets) &&
519524
content.root.equals(that.content.root) &&
520525
source.equals(that.source) &&
526+
properties.equals(that.properties) &&
521527
state.equals(that.state)
522528
case _ => false
523529
}
@@ -610,6 +616,30 @@ object IndexLogEntry {
610616
val VERSION: String = "0.1"
611617

612618
def schemaString(schema: StructType): String = schema.json
619+
620+
/**
621+
* Use this method to create a new IndexLogEntry, which automatically includes
622+
* all common default hyperspace project properties.
623+
* TODO: force dev to use this method as this takes into account all
624+
* project properties that needed to be added by default. Currently, dev can also
625+
* create IndexLogEntry from case class.
626+
* https://github.com/microsoft/hyperspace/issues/370
627+
* Also add require for hyperspace project version when we introduce breaking change.
628+
*/
629+
def create(
630+
name: String,
631+
derivedDataset: CoveringIndex,
632+
content: Content,
633+
source: Source,
634+
properties: Map[String, String]): IndexLogEntry = {
635+
IndexLogEntry(
636+
name,
637+
derivedDataset,
638+
content,
639+
source,
640+
properties + ((IndexConstants.HYPERSPACE_VERSION_PROPERTY, BuildInfo.version))
641+
)
642+
}
613643
}
614644

615645
/**
@@ -629,7 +659,7 @@ class FileIdTracker {
629659

630660
def getMaxFileId: Long = maxId
631661

632-
def getFileToIdMap: HashMap[key, Long] = fileToIdMap
662+
def getFileToIdMapping: Seq[(key, Long)] = fileToIdMap.toSeq
633663

634664
def getFileId(path: String, size: Long, modifiedTime: Long): Option[Long] =
635665
fileToIdMap.get((path, size, modifiedTime))

0 commit comments

Comments
 (0)