Skip to content

Commit cb3acbe

Browse files
committed
[SPARK-11787] [SQL] [FOLLOW-UP] Clean for this patch.
This mainly moves SqlNewHadoopRDD to the sql package. There is some state that is shared between core and I've left that in core. This allows some other associated minor cleanup.
1 parent 47d1c23 commit cb3acbe

8 files changed

Lines changed: 134 additions & 92 deletions

File tree

core/src/main/scala/org/apache/spark/rdd/HadoopRDD.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,8 @@ class HadoopRDD[K, V](
215215

216216
// Sets the thread local variable for the file's name
217217
split.inputSplit.value match {
218-
case fs: FileSplit => SqlNewHadoopRDD.setInputFileName(fs.getPath.toString)
219-
case _ => SqlNewHadoopRDD.unsetInputFileName()
218+
case fs: FileSplit => SqlNewHadoopRDDState.setInputFileName(fs.getPath.toString)
219+
case _ => SqlNewHadoopRDDState.unsetInputFileName()
220220
}
221221

222222
// Find a function that will return the FileSystem bytes read by this thread. Do this before
@@ -256,7 +256,7 @@ class HadoopRDD[K, V](
256256

257257
override def close() {
258258
if (reader != null) {
259-
SqlNewHadoopRDD.unsetInputFileName()
259+
SqlNewHadoopRDDState.unsetInputFileName()
260260
// Close the reader and release it. Note: it's very important that we don't close the
261261
// reader more than once, since that exposes us to MAPREDUCE-5918 when running against
262262
// Hadoop 1.x and older Hadoop 2.x releases. That bug can lead to non-deterministic
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.rdd
19+
20+
import org.apache.spark.unsafe.types.UTF8String
21+
22+
/**
23+
* State for SqlNewHadoopRDD objects. This is split this way because of the package splits.
24+
* TODO: Move/Combine this with org.apache.spark.sql.datasources.SqlNewHadoopRDD
25+
*/
26+
private[spark] object SqlNewHadoopRDDState {
27+
/**
28+
* The thread variable for the name of the current file being read. This is used by
29+
* the InputFileName function in Spark SQL.
30+
*/
31+
private[this] val inputFileName: ThreadLocal[UTF8String] = new ThreadLocal[UTF8String] {
32+
override protected def initialValue(): UTF8String = UTF8String.fromString("")
33+
}
34+
35+
def getInputFileName(): UTF8String = inputFileName.get()
36+
37+
private[spark] def setInputFileName(file: String) = inputFileName.set(UTF8String.fromString(file))
38+
39+
private[spark] def unsetInputFileName(): Unit = inputFileName.remove()
40+
41+
}

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/InputFileName.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package org.apache.spark.sql.catalyst.expressions
1919

20-
import org.apache.spark.rdd.SqlNewHadoopRDD
20+
import org.apache.spark.rdd.SqlNewHadoopRDDState
2121
import org.apache.spark.sql.catalyst.InternalRow
2222
import org.apache.spark.sql.catalyst.expressions.codegen.{GeneratedExpressionCode, CodeGenContext}
2323
import org.apache.spark.sql.types.{DataType, StringType}
@@ -37,7 +37,7 @@ case class InputFileName() extends LeafExpression with Nondeterministic {
3737
override protected def initInternal(): Unit = {}
3838

3939
override protected def evalInternal(input: InternalRow): UTF8String = {
40-
SqlNewHadoopRDD.getInputFileName()
40+
SqlNewHadoopRDDState.getInputFileName()
4141
}
4242

4343
override def genCode(ctx: CodeGenContext, ev: GeneratedExpressionCode): String = {

sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/UnsafeRowParquetRecordReader.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,19 @@ public class UnsafeRowParquetRecordReader extends SpecificParquetRecordReaderBas
108108
*/
109109
private static final int DEFAULT_VAR_LEN_SIZE = 32;
110110

111+
/**
112+
* Tries to initialize the reader for this split. Returns true if this reader supports reading
113+
* this split and false otherwise.
114+
*/
115+
public boolean tryInitialize(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) {
116+
try {
117+
initialize(inputSplit, taskAttemptContext);
118+
return true;
119+
} catch (Exception e) {
120+
return false;
121+
}
122+
}
123+
111124
/**
112125
* Implementation of RecordReader API.
113126
*/

sql/core/src/main/scala/org/apache/spark/sql/SQLConf.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,11 @@ private[spark] object SQLConf {
323323
"option must be set in Hadoop Configuration. 2. This option overrides " +
324324
"\"spark.sql.sources.outputCommitterClass\".")
325325

326+
val PARQUET_UNSAFE_ROW_RECORD_READER_ENABLED = booleanConf(
327+
key = "spark.sql.parquet.enableUnsafeRowRecordReader",
328+
defaultValue = Some(true),
329+
doc = "Enables using the custom ParquetUnsafeRowRecordReader.")
330+
326331
val ORC_FILTER_PUSHDOWN_ENABLED = booleanConf("spark.sql.orc.filterPushdown",
327332
defaultValue = Some(false),
328333
doc = "When true, enable filter pushdown for ORC files.")

core/src/main/scala/org/apache/spark/rdd/SqlNewHadoopRDD.scala renamed to sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/SqlNewHadoopRDD.scala

Lines changed: 47 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ package org.apache.spark.rdd
2020
import java.text.SimpleDateFormat
2121
import java.util.Date
2222

23+
import scala.reflect.ClassTag
24+
2325
import org.apache.hadoop.conf.{Configurable, Configuration}
2426
import org.apache.hadoop.io.Writable
2527
import org.apache.hadoop.mapreduce._
@@ -28,18 +30,17 @@ import org.apache.spark.broadcast.Broadcast
2830
import org.apache.spark.deploy.SparkHadoopUtil
2931
import org.apache.spark.executor.DataReadMethod
3032
import org.apache.spark.mapreduce.SparkHadoopMapReduceUtil
33+
import org.apache.spark.sql.{SQLConf, SQLContext}
34+
import org.apache.spark.sql.execution.datasources.parquet.UnsafeRowParquetRecordReader
3135
import org.apache.spark.storage.StorageLevel
32-
import org.apache.spark.unsafe.types.UTF8String
33-
import org.apache.spark.util.{Utils, SerializableConfiguration, ShutdownHookManager}
36+
import org.apache.spark.util.{SerializableConfiguration, ShutdownHookManager}
3437
import org.apache.spark.{Partition => SparkPartition, _}
3538

36-
import scala.reflect.ClassTag
37-
3839

3940
private[spark] class SqlNewHadoopPartition(
40-
rddId: Int,
41-
val index: Int,
42-
rawSplit: InputSplit with Writable)
41+
rddId: Int,
42+
val index: Int,
43+
rawSplit: InputSplit with Writable)
4344
extends SparkPartition {
4445

4546
val serializableHadoopSplit = new SerializableWritable(rawSplit)
@@ -48,26 +49,26 @@ private[spark] class SqlNewHadoopPartition(
4849
}
4950

5051
/**
51-
* An RDD that provides core functionality for reading data stored in Hadoop (e.g., files in HDFS,
52-
* sources in HBase, or S3), using the new MapReduce API (`org.apache.hadoop.mapreduce`).
53-
* It is based on [[org.apache.spark.rdd.NewHadoopRDD]]. It has three additions.
54-
* 1. A shared broadcast Hadoop Configuration.
55-
* 2. An optional closure `initDriverSideJobFuncOpt` that set configurations at the driver side
56-
* to the shared Hadoop Configuration.
57-
* 3. An optional closure `initLocalJobFuncOpt` that set configurations at both the driver side
58-
* and the executor side to the shared Hadoop Configuration.
59-
*
60-
* Note: This is RDD is basically a cloned version of [[org.apache.spark.rdd.NewHadoopRDD]] with
61-
* changes based on [[org.apache.spark.rdd.HadoopRDD]].
62-
*/
52+
* An RDD that provides core functionality for reading data stored in Hadoop (e.g., files in HDFS,
53+
* sources in HBase, or S3), using the new MapReduce API (`org.apache.hadoop.mapreduce`).
54+
* It is based on [[org.apache.spark.rdd.NewHadoopRDD]]. It has three additions.
55+
* 1. A shared broadcast Hadoop Configuration.
56+
* 2. An optional closure `initDriverSideJobFuncOpt` that set configurations at the driver side
57+
* to the shared Hadoop Configuration.
58+
* 3. An optional closure `initLocalJobFuncOpt` that set configurations at both the driver side
59+
* and the executor side to the shared Hadoop Configuration.
60+
*
61+
* Note: This is RDD is basically a cloned version of [[org.apache.spark.rdd.NewHadoopRDD]] with
62+
* changes based on [[org.apache.spark.rdd.HadoopRDD]].
63+
*/
6364
private[spark] class SqlNewHadoopRDD[V: ClassTag](
64-
sc : SparkContext,
65-
broadcastedConf: Broadcast[SerializableConfiguration],
66-
@transient private val initDriverSideJobFuncOpt: Option[Job => Unit],
67-
initLocalJobFuncOpt: Option[Job => Unit],
68-
inputFormatClass: Class[_ <: InputFormat[Void, V]],
69-
valueClass: Class[V])
70-
extends RDD[V](sc, Nil)
65+
sqlContext: SQLContext,
66+
broadcastedConf: Broadcast[SerializableConfiguration],
67+
@transient private val initDriverSideJobFuncOpt: Option[Job => Unit],
68+
initLocalJobFuncOpt: Option[Job => Unit],
69+
inputFormatClass: Class[_ <: InputFormat[Void, V]],
70+
valueClass: Class[V])
71+
extends RDD[V](sqlContext.sparkContext, Nil)
7172
with SparkHadoopMapReduceUtil
7273
with Logging {
7374

@@ -99,7 +100,7 @@ private[spark] class SqlNewHadoopRDD[V: ClassTag](
99100
// If true, enable using the custom RecordReader for parquet. This only works for
100101
// a subset of the types (no complex types).
101102
protected val enableUnsafeRowParquetReader: Boolean =
102-
sc.conf.getBoolean("spark.parquet.enableUnsafeRowRecordReader", true)
103+
sqlContext.getConf(SQLConf.PARQUET_UNSAFE_ROW_RECORD_READER_ENABLED.key).toBoolean
103104

104105
override def getPartitions: Array[SparkPartition] = {
105106
val conf = getConf(isDriverSide = true)
@@ -120,8 +121,8 @@ private[spark] class SqlNewHadoopRDD[V: ClassTag](
120121
}
121122

122123
override def compute(
123-
theSplit: SparkPartition,
124-
context: TaskContext): Iterator[V] = {
124+
theSplit: SparkPartition,
125+
context: TaskContext): Iterator[V] = {
125126
val iter = new Iterator[V] {
126127
val split = theSplit.asInstanceOf[SqlNewHadoopPartition]
127128
logInfo("Input split: " + split.serializableHadoopSplit)
@@ -132,8 +133,8 @@ private[spark] class SqlNewHadoopRDD[V: ClassTag](
132133

133134
// Sets the thread local variable for the file's name
134135
split.serializableHadoopSplit.value match {
135-
case fs: FileSplit => SqlNewHadoopRDD.setInputFileName(fs.getPath.toString)
136-
case _ => SqlNewHadoopRDD.unsetInputFileName()
136+
case fs: FileSplit => SqlNewHadoopRDDState.setInputFileName(fs.getPath.toString)
137+
case _ => SqlNewHadoopRDDState.unsetInputFileName()
137138
}
138139

139140
// Find a function that will return the FileSystem bytes read by this thread. Do this before
@@ -163,15 +164,13 @@ private[spark] class SqlNewHadoopRDD[V: ClassTag](
163164
* TODO: plumb this through a different way?
164165
*/
165166
if (enableUnsafeRowParquetReader &&
166-
format.getClass.getName == "org.apache.parquet.hadoop.ParquetInputFormat") {
167-
// TODO: move this class to sql.execution and remove this.
168-
reader = Utils.classForName(
169-
"org.apache.spark.sql.execution.datasources.parquet.UnsafeRowParquetRecordReader")
170-
.newInstance().asInstanceOf[RecordReader[Void, V]]
171-
try {
172-
reader.initialize(split.serializableHadoopSplit.value, hadoopAttemptContext)
173-
} catch {
174-
case e: Exception => reader = null
167+
format.getClass.getName == "org.apache.parquet.hadoop.ParquetInputFormat") {
168+
val parquetReader: UnsafeRowParquetRecordReader = new UnsafeRowParquetRecordReader()
169+
if (!parquetReader.tryInitialize(
170+
split.serializableHadoopSplit.value, hadoopAttemptContext)) {
171+
parquetReader.close()
172+
} else {
173+
reader = parquetReader.asInstanceOf[RecordReader[Void, V]]
175174
}
176175
}
177176

@@ -217,7 +216,7 @@ private[spark] class SqlNewHadoopRDD[V: ClassTag](
217216

218217
private def close() {
219218
if (reader != null) {
220-
SqlNewHadoopRDD.unsetInputFileName()
219+
SqlNewHadoopRDDState.unsetInputFileName()
221220
// Close the reader and release it. Note: it's very important that we don't close the
222221
// reader more than once, since that exposes us to MAPREDUCE-5918 when running against
223222
// Hadoop 1.x and older Hadoop 2.x releases. That bug can lead to non-deterministic
@@ -235,7 +234,7 @@ private[spark] class SqlNewHadoopRDD[V: ClassTag](
235234
if (bytesReadCallback.isDefined) {
236235
inputMetrics.updateBytesRead()
237236
} else if (split.serializableHadoopSplit.value.isInstanceOf[FileSplit] ||
238-
split.serializableHadoopSplit.value.isInstanceOf[CombineFileSplit]) {
237+
split.serializableHadoopSplit.value.isInstanceOf[CombineFileSplit]) {
239238
// If we can't get the bytes read from the FS stats, fall back to the split size,
240239
// which may be inaccurate.
241240
try {
@@ -276,32 +275,15 @@ private[spark] class SqlNewHadoopRDD[V: ClassTag](
276275
}
277276
super.persist(storageLevel)
278277
}
279-
}
280-
281-
private[spark] object SqlNewHadoopRDD {
282-
283-
/**
284-
* The thread variable for the name of the current file being read. This is used by
285-
* the InputFileName function in Spark SQL.
286-
*/
287-
private[this] val inputFileName: ThreadLocal[UTF8String] = new ThreadLocal[UTF8String] {
288-
override protected def initialValue(): UTF8String = UTF8String.fromString("")
289-
}
290-
291-
def getInputFileName(): UTF8String = inputFileName.get()
292-
293-
private[spark] def setInputFileName(file: String) = inputFileName.set(UTF8String.fromString(file))
294-
295-
private[spark] def unsetInputFileName(): Unit = inputFileName.remove()
296278

297279
/**
298-
* Analogous to [[org.apache.spark.rdd.MapPartitionsRDD]], but passes in an InputSplit to
299-
* the given function rather than the index of the partition.
300-
*/
280+
* Analogous to [[org.apache.spark.rdd.MapPartitionsRDD]], but passes in an InputSplit to
281+
* the given function rather than the index of the partition.
282+
*/
301283
private[spark] class NewHadoopMapPartitionsWithSplitRDD[U: ClassTag, T: ClassTag](
302-
prev: RDD[T],
303-
f: (InputSplit, Iterator[T]) => Iterator[U],
304-
preservesPartitioning: Boolean = false)
284+
prev: RDD[T],
285+
f: (InputSplit, Iterator[T]) => Iterator[U],
286+
preservesPartitioning: Boolean = false)
305287
extends RDD[U](prev) {
306288

307289
override val partitioner = if (preservesPartitioning) firstParent[T].partitioner else None

sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRelation.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ private[sql] class ParquetRelation(
319319

320320
Utils.withDummyCallSite(sqlContext.sparkContext) {
321321
new SqlNewHadoopRDD(
322-
sc = sqlContext.sparkContext,
322+
sqlContext = sqlContext,
323323
broadcastedConf = broadcastedConf,
324324
initDriverSideJobFuncOpt = Some(setInputPaths),
325325
initLocalJobFuncOpt = Some(initLocalJobFuncOpt),

sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFilterSuite.scala

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -337,29 +337,30 @@ class ParquetFilterSuite extends QueryTest with ParquetTest with SharedSQLContex
337337
}
338338
}
339339

340-
// Renable when we can toggle custom ParquetRecordReader on/off. The custom reader does
341-
// not do row by row filtering (and we probably don't want to push that).
342-
ignore("SPARK-11661 Still pushdown filters returned by unhandledFilters") {
340+
// The unsafe row RecordReader does not support row by row filtering so run it with it disabled.
341+
test("SPARK-11661 Still pushdown filters returned by unhandledFilters") {
343342
import testImplicits._
344343
withSQLConf(SQLConf.PARQUET_FILTER_PUSHDOWN_ENABLED.key -> "true") {
345-
withTempPath { dir =>
346-
val path = s"${dir.getCanonicalPath}/part=1"
347-
(1 to 3).map(i => (i, i.toString)).toDF("a", "b").write.parquet(path)
348-
val df = sqlContext.read.parquet(path).filter("a = 2")
349-
350-
// This is the source RDD without Spark-side filtering.
351-
val childRDD =
352-
df
353-
.queryExecution
354-
.executedPlan.asInstanceOf[org.apache.spark.sql.execution.Filter]
355-
.child
356-
.execute()
357-
358-
// The result should be single row.
359-
// When a filter is pushed to Parquet, Parquet can apply it to every row.
360-
// So, we can check the number of rows returned from the Parquet
361-
// to make sure our filter pushdown work.
362-
assert(childRDD.count == 1)
344+
withSQLConf(SQLConf.PARQUET_UNSAFE_ROW_RECORD_READER_ENABLED.key -> "false") {
345+
withTempPath { dir =>
346+
val path = s"${dir.getCanonicalPath}/part=1"
347+
(1 to 3).map(i => (i, i.toString)).toDF("a", "b").write.parquet(path)
348+
val df = sqlContext.read.parquet(path).filter("a = 2")
349+
350+
// This is the source RDD without Spark-side filtering.
351+
val childRDD =
352+
df
353+
.queryExecution
354+
.executedPlan.asInstanceOf[org.apache.spark.sql.execution.Filter]
355+
.child
356+
.execute()
357+
358+
// The result should be single row.
359+
// When a filter is pushed to Parquet, Parquet can apply it to every row.
360+
// So, we can check the number of rows returned from the Parquet
361+
// to make sure our filter pushdown work.
362+
assert(childRDD.count == 1)
363+
}
363364
}
364365
}
365366
}

0 commit comments

Comments
 (0)