Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import com.google.common.base.Objects
import org.apache.spark.sql.AnalysisException
import org.apache.spark.sql.catalyst.{FunctionIdentifier, InternalRow, TableIdentifier}
import org.apache.spark.sql.catalyst.analysis.MultiInstanceRelation
import org.apache.spark.sql.catalyst.expressions.{Attribute, AttributeMap, Cast, Literal}
import org.apache.spark.sql.catalyst.expressions.{Attribute, AttributeMap, AttributeReference, Cast, Literal}
import org.apache.spark.sql.catalyst.plans.logical._
import org.apache.spark.sql.catalyst.util.{CaseInsensitiveMap, DateTimeUtils}
import org.apache.spark.sql.catalyst.util.quoteIdentifier
Expand Down Expand Up @@ -403,14 +403,14 @@ object CatalogTypes {
*/
case class CatalogRelation(
tableMeta: CatalogTable,
dataCols: Seq[Attribute],
partitionCols: Seq[Attribute]) extends LeafNode with MultiInstanceRelation {
dataCols: Seq[AttributeReference],
partitionCols: Seq[AttributeReference]) extends LeafNode with MultiInstanceRelation {
assert(tableMeta.identifier.database.isDefined)
assert(tableMeta.partitionSchema.sameType(partitionCols.toStructType))
assert(tableMeta.dataSchema.sameType(dataCols.toStructType))

// The partition column should always appear after data columns.
override def output: Seq[Attribute] = dataCols ++ partitionCols
override def output: Seq[AttributeReference] = dataCols ++ partitionCols

def isPartitioned: Boolean = partitionCols.nonEmpty

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,16 +231,17 @@ class FindDataSourceTable(sparkSession: SparkSession) extends Rule[LogicalPlan]
options = table.storage.properties ++ pathOption,
catalogTable = Some(table))

LogicalRelation(
dataSource.resolveRelation(checkFilesExist = false),
catalogTable = Some(table))
LogicalRelation(dataSource.resolveRelation(checkFilesExist = false), table)
}
}).asInstanceOf[LogicalRelation]

// It's possible that the table schema is empty and need to be inferred at runtime. We should
// not specify expected outputs for this case.
val expectedOutputs = if (r.output.isEmpty) None else Some(r.output)
plan.copy(expectedOutputAttributes = expectedOutputs)
if (r.output.isEmpty) {
// It's possible that the table schema is empty and need to be inferred at runtime. For this
// case, we don't need to change the output of the cached plan.
plan
} else {
plan.copy(output = r.output)
}
}

override def apply(plan: LogicalPlan): LogicalPlan = plan transform {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,21 @@ package org.apache.spark.sql.execution.datasources

import org.apache.spark.sql.catalyst.analysis.MultiInstanceRelation
import org.apache.spark.sql.catalyst.catalog.CatalogTable
import org.apache.spark.sql.catalyst.expressions.{Attribute, AttributeMap, AttributeReference}
import org.apache.spark.sql.catalyst.expressions.{AttributeMap, AttributeReference}
import org.apache.spark.sql.catalyst.plans.logical.{LeafNode, LogicalPlan, Statistics}
import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.sources.BaseRelation
import org.apache.spark.util.Utils

/**
* Used to link a [[BaseRelation]] in to a logical query plan.
*
* Note that sometimes we need to use `LogicalRelation` to replace an existing leaf node without
* changing the output attributes' IDs. The `expectedOutputAttributes` parameter is used for
* this purpose. See https://issues.apache.org/jira/browse/SPARK-10741 for more details.
*/
case class LogicalRelation(
relation: BaseRelation,
expectedOutputAttributes: Option[Seq[Attribute]] = None,
catalogTable: Option[CatalogTable] = None)
output: Seq[AttributeReference],
catalogTable: Option[CatalogTable])
extends LeafNode with MultiInstanceRelation {

override val output: Seq[AttributeReference] = {
val attrs = relation.schema.toAttributes
expectedOutputAttributes.map { expectedAttrs =>
assert(expectedAttrs.length == attrs.length)
attrs.zip(expectedAttrs).map {
// We should respect the attribute names provided by base relation and only use the
// exprId in `expectedOutputAttributes`.
// The reason is that, some relations(like parquet) will reconcile attribute names to
// workaround case insensitivity issue.
case (attr, expected) => attr.withExprId(expected.exprId)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like this logics mentioned in the comments is removed by this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch! I found this logic is only useful when converting hive tables to data source tables, so I moved the logic there: https://github.com/apache/spark/pull/17552/files#diff-ee66e11b56c21364760a5ed2b783f863R215

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree.

}
}.getOrElse(attrs)
}

// Logical Relations are distinct if they have different output for the sake of transformations.
override def equals(other: Any): Boolean = other match {
case l @ LogicalRelation(otherRelation, _, _) => relation == otherRelation && output == l.output
Expand Down Expand Up @@ -87,11 +69,8 @@ case class LogicalRelation(
* unique expression ids. We respect the `expectedOutputAttributes` and create
* new instances of attributes in it.
*/
override def newInstance(): this.type = {
LogicalRelation(
relation,
expectedOutputAttributes.map(_.map(_.newInstance())),
catalogTable).asInstanceOf[this.type]
override def newInstance(): LogicalRelation = {
this.copy(output = output.map(_.newInstance()))
}

override def refresh(): Unit = relation match {
Expand All @@ -101,3 +80,11 @@ case class LogicalRelation(

override def simpleString: String = s"Relation[${Utils.truncatedString(output, ",")}] $relation"
}

object LogicalRelation {
def apply(relation: BaseRelation): LogicalRelation =
LogicalRelation(relation, relation.schema.toAttributes, None)

def apply(relation: BaseRelation, table: CatalogTable): LogicalRelation =
LogicalRelation(relation, relation.schema.toAttributes, Some(table))
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ private[sql] object PruneFileSourcePartitions extends Rule[LogicalPlan] {
val prunedFileIndex = catalogFileIndex.filterPartitions(partitionKeyFilters.toSeq)
val prunedFsRelation =
fsRelation.copy(location = prunedFileIndex)(sparkSession)
val prunedLogicalRelation = logicalRelation.copy(
relation = prunedFsRelation,
expectedOutputAttributes = Some(logicalRelation.output))
val prunedLogicalRelation = logicalRelation.copy(relation = prunedFsRelation)

// Keep partition-pruning predicates so that they are visible in physical planning
val filterExpression = filters.reduceLeft(And)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ class PathOptionSuite extends DataSourceTest with SharedSQLContext {
|USING ${classOf[TestOptionsSource].getCanonicalName}
|OPTIONS (PATH '/tmp/path')
""".stripMargin)
assert(getPathOption("src") == Some("file:/tmp/path"))
assert(getPathOption("src").map(makeQualifiedPath) == Some(makeQualifiedPath("/tmp/path")))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes in this test suite are not related to this PR, just to reinforce it.

}

// should exist even path option is not specified when creating table
withTable("src") {
sql(s"CREATE TABLE src(i int) USING ${classOf[TestOptionsSource].getCanonicalName}")
assert(getPathOption("src") == Some(CatalogUtils.URIToString(defaultTablePath("src"))))
assert(getPathOption("src").map(makeQualifiedPath) == Some(defaultTablePath("src")))
}
}

Expand All @@ -95,9 +95,9 @@ class PathOptionSuite extends DataSourceTest with SharedSQLContext {
|OPTIONS (PATH '$p')
|AS SELECT 1
""".stripMargin)
assert(CatalogUtils.stringToURI(
spark.table("src").schema.head.metadata.getString("path")) ==
makeQualifiedPath(p.getAbsolutePath))
assert(
spark.table("src").schema.head.metadata.getString("path") ==
p.getAbsolutePath)
}
}

Expand All @@ -109,8 +109,9 @@ class PathOptionSuite extends DataSourceTest with SharedSQLContext {
|USING ${classOf[TestOptionsSource].getCanonicalName}
|AS SELECT 1
""".stripMargin)
assert(spark.table("src").schema.head.metadata.getString("path") ==
CatalogUtils.URIToString(defaultTablePath("src")))
assert(
makeQualifiedPath(spark.table("src").schema.head.metadata.getString("path")) ==
defaultTablePath("src"))
}
}

Expand All @@ -122,13 +123,13 @@ class PathOptionSuite extends DataSourceTest with SharedSQLContext {
|USING ${classOf[TestOptionsSource].getCanonicalName}
|OPTIONS (PATH '/tmp/path')""".stripMargin)
sql("ALTER TABLE src SET LOCATION '/tmp/path2'")
assert(getPathOption("src") == Some("/tmp/path2"))
assert(getPathOption("src").map(makeQualifiedPath) == Some(makeQualifiedPath("/tmp/path2")))
}

withTable("src", "src2") {
sql(s"CREATE TABLE src(i int) USING ${classOf[TestOptionsSource].getCanonicalName}")
sql("ALTER TABLE src RENAME TO src2")
assert(getPathOption("src2") == Some(CatalogUtils.URIToString(defaultTablePath("src2"))))
assert(getPathOption("src2").map(makeQualifiedPath) == Some(defaultTablePath("src2")))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ private[hive] class HiveMetastoreCatalog(sparkSession: SparkSession) extends Log
bucketSpec = None,
fileFormat = fileFormat,
options = options)(sparkSession = sparkSession)
val created = LogicalRelation(fsRelation, catalogTable = Some(updatedTable))
val created = LogicalRelation(fsRelation, updatedTable)
tableRelationCache.put(tableIdentifier, created)
created
}
Expand Down Expand Up @@ -203,7 +203,7 @@ private[hive] class HiveMetastoreCatalog(sparkSession: SparkSession) extends Log
bucketSpec = None,
options = options,
className = fileType).resolveRelation(),
catalogTable = Some(updatedTable))
table = updatedTable)

tableRelationCache.put(tableIdentifier, created)
created
Expand All @@ -212,7 +212,14 @@ private[hive] class HiveMetastoreCatalog(sparkSession: SparkSession) extends Log
logicalRelation
})
}
result.copy(expectedOutputAttributes = Some(relation.output))
// The inferred schema may have different filed names as the table schema, we should respect
// it, but also respect the exprId in table relation output.
assert(result.output.length == relation.output.length &&
result.output.zip(relation.output).forall { case (a1, a2) => a1.dataType == a2.dataType })
val newOutput = result.output.zip(relation.output).map {
case (a1, a2) => a1.withExprId(a2.exprId)
}
result.copy(output = newOutput)
}

private def inferIfNeeded(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ class CachedTableSuite extends QueryTest with SQLTestUtils with TestHiveSingleto
fileFormat = new ParquetFileFormat(),
options = Map.empty)(sparkSession = spark)

val plan = LogicalRelation(relation, catalogTable = Some(tableMeta))
val plan = LogicalRelation(relation, tableMeta)
spark.sharedState.cacheManager.cacheQuery(Dataset.ofRows(spark, plan))

assert(spark.sharedState.cacheManager.lookupCachedData(plan).isDefined)
Expand All @@ -342,7 +342,7 @@ class CachedTableSuite extends QueryTest with SQLTestUtils with TestHiveSingleto
bucketSpec = None,
fileFormat = new ParquetFileFormat(),
options = Map.empty)(sparkSession = spark)
val samePlan = LogicalRelation(sameRelation, catalogTable = Some(tableMeta))
val samePlan = LogicalRelation(sameRelation, tableMeta)

assert(spark.sharedState.cacheManager.lookupCachedData(samePlan).isDefined)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class PruneFileSourcePartitionsSuite extends QueryTest with SQLTestUtils with Te
fileFormat = new ParquetFileFormat(),
options = Map.empty)(sparkSession = spark)

val logicalRelation = LogicalRelation(relation, catalogTable = Some(tableMeta))
val logicalRelation = LogicalRelation(relation, tableMeta)
val query = Project(Seq('i, 'p), Filter('p === 1, logicalRelation)).analyze

val optimized = Optimize.execute(query)
Expand Down