Skip to content

Commit e41ecef

Browse files
MaxGekkHyukjinKwon
authored andcommitted
[SPARK-36034][SQL][3.0] Rebase datetime in pushed down filters to parquet
### What changes were proposed in this pull request? In the PR, I propose to propagate either the SQL config `spark.sql.parquet.datetimeRebaseModeInRead` or/and Parquet option `datetimeRebaseMode` to `ParquetFilters`. The `ParquetFilters` class uses the settings in conversions of dates/timestamps instances from datasource filters to values pushed via `FilterApi` to the `parquet-column` lib. Before the changes, date/timestamp values expressed as days/microseconds/milliseconds are interpreted as offsets in Proleptic Gregorian calendar, and pushed to the parquet library as is. That works fine if timestamp/dates values in parquet files were saved in the `CORRECTED` mode but in the `LEGACY` mode, filter's values could not match to actual values. After the changes, timestamp/dates values of filters pushed down to parquet libs such as `FilterApi.eq(col1, -719162)` are rebased according the rebase settings. For the example, if the rebase mode is `CORRECTED`, **-719162** is pushed down as is but if the current rebase mode is `LEGACY`, the number of days is rebased to **-719164**. For more context, the PR description #28067 shows the diffs between two calendars. ### Why are the changes needed? The changes fix the bug portrayed by the following example from SPARK-36034: ```scala In [27]: spark.conf.set("spark.sql.legacy.parquet.datetimeRebaseModeInWrite", "LEGACY") >>> spark.sql("SELECT DATE '0001-01-01' AS date").write.mode("overwrite").parquet("date_written_by_spark3_legacy") >>> spark.read.parquet("date_written_by_spark3_legacy").where("date = '0001-01-01'").show() +----+ |date| +----+ +----+ ``` The result must have the date value `0001-01-01`. ### Does this PR introduce _any_ user-facing change? In some sense, yes. Query results can be different in some cases. For the example above: ```scala scala> spark.conf.set("spark.sql.parquet.datetimeRebaseModeInWrite", "LEGACY") scala> spark.sql("SELECT DATE '0001-01-01' AS date").write.mode("overwrite").parquet("date_written_by_spark3_legacy") scala> spark.read.parquet("date_written_by_spark3_legacy").where("date = '0001-01-01'").show(false) +----------+ |date | +----------+ |0001-01-01| +----------+ ``` ### How was this patch tested? By running the modified test suite `ParquetFilterSuite`: ``` $ build/sbt "test:testOnly *ParquetV1FilterSuite" $ build/sbt "test:testOnly *ParquetV2FilterSuite" ``` Authored-by: Max Gekk <max.gekkgmail.com> Signed-off-by: Hyukjin Kwon <gurwls223apache.org> (cherry picked from commit b09b7f7) (cherry picked from commit ba71172) Closes #33387 from MaxGekk/fix-parquet-ts-filter-pushdown-3.0. Authored-by: Max Gekk <[email protected]> Signed-off-by: Hyukjin Kwon <[email protected]>
1 parent 86a9059 commit e41ecef

File tree

5 files changed

+134
-86
lines changed

5 files changed

+134
-86
lines changed

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,21 @@ class ParquetFileFormat
270270

271271
lazy val footerFileMetaData =
272272
ParquetFileReader.readFooter(sharedConf, filePath, SKIP_ROW_GROUPS).getFileMetaData
273+
val datetimeRebaseMode = DataSourceUtils.datetimeRebaseMode(
274+
footerFileMetaData.getKeyValueMetaData.get,
275+
SQLConf.get.getConf(SQLConf.LEGACY_PARQUET_REBASE_MODE_IN_READ))
273276
// Try to push down filters when filter push-down is enabled.
274277
val pushed = if (enableParquetFilterPushDown) {
275278
val parquetSchema = footerFileMetaData.getSchema
276-
val parquetFilters = new ParquetFilters(parquetSchema, pushDownDate, pushDownTimestamp,
277-
pushDownDecimal, pushDownStringStartWith, pushDownInFilterThreshold, isCaseSensitive)
279+
val parquetFilters = new ParquetFilters(
280+
parquetSchema,
281+
pushDownDate,
282+
pushDownTimestamp,
283+
pushDownDecimal,
284+
pushDownStringStartWith,
285+
pushDownInFilterThreshold,
286+
isCaseSensitive,
287+
datetimeRebaseMode)
278288
filters
279289
// Collects all converted Parquet filter predicates. Notice that not all predicates can be
280290
// converted (`ParquetFilters.createFilter` returns an `Option`). That's why a `flatMap`
@@ -300,10 +310,6 @@ class ParquetFileFormat
300310
None
301311
}
302312

303-
val datetimeRebaseMode = DataSourceUtils.datetimeRebaseMode(
304-
footerFileMetaData.getKeyValueMetaData.get,
305-
SQLConf.get.getConf(SQLConf.LEGACY_PARQUET_REBASE_MODE_IN_READ))
306-
307313
val attemptId = new TaskAttemptID(new TaskID(new JobID(), TaskType.MAP, 0), 0)
308314
val hadoopAttemptContext =
309315
new TaskAttemptContextImpl(broadcastedHadoopConf.value.value, attemptId)

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName._
3535

3636
import org.apache.spark.sql.catalyst.util.{CaseInsensitiveMap, DateTimeUtils}
3737
import org.apache.spark.sql.catalyst.util.DateTimeUtils.SQLDate
38+
import org.apache.spark.sql.catalyst.util.RebaseDateTime.{rebaseGregorianToJulianDays, rebaseGregorianToJulianMicros}
39+
import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
3840
import org.apache.spark.sql.sources
3941
import org.apache.spark.unsafe.types.UTF8String
4042

@@ -48,7 +50,8 @@ class ParquetFilters(
4850
pushDownDecimal: Boolean,
4951
pushDownStartWith: Boolean,
5052
pushDownInFilterThreshold: Int,
51-
caseSensitive: Boolean) {
53+
caseSensitive: Boolean,
54+
datetimeRebaseMode: LegacyBehaviorPolicy.Value) {
5255
// A map which contains parquet field name and data type, if predicate push down applies.
5356
//
5457
// Each key in `nameToParquetField` represents a column; `dots` are used as separators for
@@ -124,14 +127,26 @@ class ParquetFilters(
124127
private val ParquetTimestampMicrosType = ParquetSchemaType(TIMESTAMP_MICROS, INT64, 0, null)
125128
private val ParquetTimestampMillisType = ParquetSchemaType(TIMESTAMP_MILLIS, INT64, 0, null)
126129

127-
private def dateToDays(date: Any): SQLDate = date match {
128-
case d: Date => DateTimeUtils.fromJavaDate(d)
129-
case ld: LocalDate => DateTimeUtils.localDateToDays(ld)
130+
private def dateToDays(date: Any): SQLDate = {
131+
val gregorianDays = date match {
132+
case d: Date => DateTimeUtils.fromJavaDate(d)
133+
case ld: LocalDate => DateTimeUtils.localDateToDays(ld)
134+
}
135+
datetimeRebaseMode match {
136+
case LegacyBehaviorPolicy.LEGACY => rebaseGregorianToJulianDays(gregorianDays)
137+
case _ => gregorianDays
138+
}
130139
}
131140

132-
private def timestampToMicros(v: Any): JLong = v match {
133-
case i: Instant => DateTimeUtils.instantToMicros(i)
134-
case t: Timestamp => DateTimeUtils.fromJavaTimestamp(t)
141+
private def timestampToMicros(v: Any): JLong = {
142+
val gregorianMicros = v match {
143+
case i: Instant => DateTimeUtils.instantToMicros(i)
144+
case t: Timestamp => DateTimeUtils.fromJavaTimestamp(t)
145+
}
146+
datetimeRebaseMode match {
147+
case LegacyBehaviorPolicy.LEGACY => rebaseGregorianToJulianMicros(gregorianMicros)
148+
case _ => gregorianMicros
149+
}
135150
}
136151

137152
private def decimalToInt32(decimal: JBigDecimal): Integer = decimal.unscaledValue().intValue()

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,21 @@ case class ParquetPartitionReaderFactory(
134134

135135
lazy val footerFileMetaData =
136136
ParquetFileReader.readFooter(conf, filePath, SKIP_ROW_GROUPS).getFileMetaData
137+
val datetimeRebaseMode = DataSourceUtils.datetimeRebaseMode(
138+
footerFileMetaData.getKeyValueMetaData.get,
139+
SQLConf.get.getConf(SQLConf.LEGACY_PARQUET_REBASE_MODE_IN_READ))
137140
// Try to push down filters when filter push-down is enabled.
138141
val pushed = if (enableParquetFilterPushDown) {
139142
val parquetSchema = footerFileMetaData.getSchema
140-
val parquetFilters = new ParquetFilters(parquetSchema, pushDownDate, pushDownTimestamp,
141-
pushDownDecimal, pushDownStringStartWith, pushDownInFilterThreshold, isCaseSensitive)
143+
val parquetFilters = new ParquetFilters(
144+
parquetSchema,
145+
pushDownDate,
146+
pushDownTimestamp,
147+
pushDownDecimal,
148+
pushDownStringStartWith,
149+
pushDownInFilterThreshold,
150+
isCaseSensitive,
151+
datetimeRebaseMode)
142152
filters
143153
// Collects all converted Parquet filter predicates. Notice that not all predicates can be
144154
// converted (`ParquetFilters.createFilter` returns an `Option`). That's why a `flatMap`
@@ -171,9 +181,6 @@ case class ParquetPartitionReaderFactory(
171181
if (pushed.isDefined) {
172182
ParquetInputFormat.setFilterPredicate(hadoopAttemptContext.getConfiguration, pushed.get)
173183
}
174-
val datetimeRebaseMode = DataSourceUtils.datetimeRebaseMode(
175-
footerFileMetaData.getKeyValueMetaData.get,
176-
SQLConf.get.getConf(SQLConf.LEGACY_PARQUET_REBASE_MODE_IN_READ))
177184
val reader = buildReaderFunc(
178185
split, file.partitionValues, hadoopAttemptContext, pushed, convertTz, datetimeRebaseMode)
179186
reader.initialize(split, hadoopAttemptContext)

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.apache.spark.sql.connector.read.{Scan, SupportsPushDownFilters}
2424
import org.apache.spark.sql.execution.datasources.PartitioningAwareFileIndex
2525
import org.apache.spark.sql.execution.datasources.parquet.{ParquetFilters, SparkToParquetSchemaConverter}
2626
import org.apache.spark.sql.execution.datasources.v2.FileScanBuilder
27+
import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
2728
import org.apache.spark.sql.sources.Filter
2829
import org.apache.spark.sql.types.StructType
2930
import org.apache.spark.sql.util.CaseInsensitiveStringMap
@@ -51,8 +52,17 @@ case class ParquetScanBuilder(
5152
val isCaseSensitive = sqlConf.caseSensitiveAnalysis
5253
val parquetSchema =
5354
new SparkToParquetSchemaConverter(sparkSession.sessionState.conf).convert(schema)
54-
val parquetFilters = new ParquetFilters(parquetSchema, pushDownDate, pushDownTimestamp,
55-
pushDownDecimal, pushDownStringStartWith, pushDownInFilterThreshold, isCaseSensitive)
55+
val parquetFilters = new ParquetFilters(
56+
parquetSchema,
57+
pushDownDate,
58+
pushDownTimestamp,
59+
pushDownDecimal,
60+
pushDownStringStartWith,
61+
pushDownInFilterThreshold,
62+
isCaseSensitive,
63+
// The rebase mode doesn't matter here because the filters are used to determine
64+
// whether they is convertible.
65+
LegacyBehaviorPolicy.CORRECTED)
5666
parquetFilters.convertibleFilters(this.filters).toArray
5767
}
5868

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

Lines changed: 76 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ import org.apache.spark.sql.execution.datasources.v2.DataSourceV2ScanRelation
4242
import org.apache.spark.sql.execution.datasources.v2.parquet.ParquetScan
4343
import org.apache.spark.sql.functions._
4444
import org.apache.spark.sql.internal.SQLConf
45-
import org.apache.spark.sql.internal.SQLConf.ParquetOutputTimestampType
45+
import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
46+
import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy.{CORRECTED, LEGACY}
47+
import org.apache.spark.sql.internal.SQLConf.ParquetOutputTimestampType.{INT96, TIMESTAMP_MICROS, TIMESTAMP_MILLIS}
4648
import org.apache.spark.sql.test.SharedSparkSession
4749
import org.apache.spark.sql.types._
4850
import org.apache.spark.tags.ExtendedSQLTest
@@ -70,11 +72,14 @@ abstract class ParquetFilterSuite extends QueryTest with ParquetTest with Shared
7072

7173
protected def createParquetFilters(
7274
schema: MessageType,
73-
caseSensitive: Option[Boolean] = None): ParquetFilters =
75+
caseSensitive: Option[Boolean] = None,
76+
datetimeRebaseMode: LegacyBehaviorPolicy.Value = LegacyBehaviorPolicy.CORRECTED
77+
): ParquetFilters =
7478
new ParquetFilters(schema, conf.parquetFilterPushDownDate, conf.parquetFilterPushDownTimestamp,
7579
conf.parquetFilterPushDownDecimal, conf.parquetFilterPushDownStringStartWith,
7680
conf.parquetFilterPushDownInFilterThreshold,
77-
caseSensitive.getOrElse(conf.caseSensitiveAnalysis))
81+
caseSensitive.getOrElse(conf.caseSensitiveAnalysis),
82+
datetimeRebaseMode)
7883

7984
override def beforeEach(): Unit = {
8085
super.beforeEach()
@@ -548,97 +553,102 @@ abstract class ParquetFilterSuite extends QueryTest with ParquetTest with Shared
548553
def date: Date = Date.valueOf(s)
549554
}
550555

551-
val data = Seq("2018-03-18", "2018-03-19", "2018-03-20", "2018-03-21")
556+
val data = Seq("1000-01-01", "2018-03-19", "2018-03-20", "2018-03-21")
552557
import testImplicits._
553558

554559
Seq(false, true).foreach { java8Api =>
555-
withSQLConf(SQLConf.DATETIME_JAVA8API_ENABLED.key -> java8Api.toString) {
556-
val dates = data.map(i => Tuple1(Date.valueOf(i))).toDF()
557-
withNestedParquetDataFrame(dates) { case (inputDF, colName, fun) =>
558-
implicit val df: DataFrame = inputDF
560+
Seq(CORRECTED, LEGACY).foreach { rebaseMode =>
561+
withSQLConf(
562+
SQLConf.DATETIME_JAVA8API_ENABLED.key -> java8Api.toString,
563+
SQLConf.LEGACY_PARQUET_REBASE_MODE_IN_WRITE.key -> rebaseMode.toString) {
564+
val dates = data.map(i => Tuple1(Date.valueOf(i))).toDF()
565+
withNestedParquetDataFrame(dates) { case (inputDF, colName, fun) =>
566+
implicit val df: DataFrame = inputDF
567+
568+
def resultFun(dateStr: String): Any = {
569+
val parsed = if (java8Api) LocalDate.parse(dateStr) else Date.valueOf(dateStr)
570+
fun(parsed)
571+
}
559572

560-
def resultFun(dateStr: String): Any = {
561-
val parsed = if (java8Api) LocalDate.parse(dateStr) else Date.valueOf(dateStr)
562-
fun(parsed)
573+
val dateAttr: Expression = df(colName).expr
574+
assert(df(colName).expr.dataType === DateType)
575+
576+
checkFilterPredicate(dateAttr.isNull, classOf[Eq[_]], Seq.empty[Row])
577+
checkFilterPredicate(dateAttr.isNotNull, classOf[NotEq[_]],
578+
data.map(i => Row.apply(resultFun(i))))
579+
580+
checkFilterPredicate(dateAttr === "1000-01-01".date, classOf[Eq[_]],
581+
resultFun("1000-01-01"))
582+
checkFilterPredicate(dateAttr <=> "1000-01-01".date, classOf[Eq[_]],
583+
resultFun("1000-01-01"))
584+
checkFilterPredicate(dateAttr =!= "1000-01-01".date, classOf[NotEq[_]],
585+
Seq("2018-03-19", "2018-03-20", "2018-03-21").map(i => Row.apply(resultFun(i))))
586+
587+
checkFilterPredicate(dateAttr < "2018-03-19".date, classOf[Lt[_]],
588+
resultFun("1000-01-01"))
589+
checkFilterPredicate(dateAttr > "2018-03-20".date, classOf[Gt[_]],
590+
resultFun("2018-03-21"))
591+
checkFilterPredicate(dateAttr <= "1000-01-01".date, classOf[LtEq[_]],
592+
resultFun("1000-01-01"))
593+
checkFilterPredicate(dateAttr >= "2018-03-21".date, classOf[GtEq[_]],
594+
resultFun("2018-03-21"))
595+
596+
checkFilterPredicate(Literal("1000-01-01".date) === dateAttr, classOf[Eq[_]],
597+
resultFun("1000-01-01"))
598+
checkFilterPredicate(Literal("1000-01-01".date) <=> dateAttr, classOf[Eq[_]],
599+
resultFun("1000-01-01"))
600+
checkFilterPredicate(Literal("2018-03-19".date) > dateAttr, classOf[Lt[_]],
601+
resultFun("1000-01-01"))
602+
checkFilterPredicate(Literal("2018-03-20".date) < dateAttr, classOf[Gt[_]],
603+
resultFun("2018-03-21"))
604+
checkFilterPredicate(Literal("1000-01-01".date) >= dateAttr, classOf[LtEq[_]],
605+
resultFun("1000-01-01"))
606+
checkFilterPredicate(Literal("2018-03-21".date) <= dateAttr, classOf[GtEq[_]],
607+
resultFun("2018-03-21"))
608+
609+
checkFilterPredicate(!(dateAttr < "2018-03-21".date), classOf[GtEq[_]],
610+
resultFun("2018-03-21"))
611+
checkFilterPredicate(
612+
dateAttr < "2018-03-19".date || dateAttr > "2018-03-20".date,
613+
classOf[Operators.Or],
614+
Seq(Row(resultFun("1000-01-01")), Row(resultFun("2018-03-21"))))
563615
}
564-
565-
val dateAttr: Expression = df(colName).expr
566-
assert(df(colName).expr.dataType === DateType)
567-
568-
checkFilterPredicate(dateAttr.isNull, classOf[Eq[_]], Seq.empty[Row])
569-
checkFilterPredicate(dateAttr.isNotNull, classOf[NotEq[_]],
570-
data.map(i => Row.apply(resultFun(i))))
571-
572-
checkFilterPredicate(dateAttr === "2018-03-18".date, classOf[Eq[_]],
573-
resultFun("2018-03-18"))
574-
checkFilterPredicate(dateAttr <=> "2018-03-18".date, classOf[Eq[_]],
575-
resultFun("2018-03-18"))
576-
checkFilterPredicate(dateAttr =!= "2018-03-18".date, classOf[NotEq[_]],
577-
Seq("2018-03-19", "2018-03-20", "2018-03-21").map(i => Row.apply(resultFun(i))))
578-
579-
checkFilterPredicate(dateAttr < "2018-03-19".date, classOf[Lt[_]],
580-
resultFun("2018-03-18"))
581-
checkFilterPredicate(dateAttr > "2018-03-20".date, classOf[Gt[_]],
582-
resultFun("2018-03-21"))
583-
checkFilterPredicate(dateAttr <= "2018-03-18".date, classOf[LtEq[_]],
584-
resultFun("2018-03-18"))
585-
checkFilterPredicate(dateAttr >= "2018-03-21".date, classOf[GtEq[_]],
586-
resultFun("2018-03-21"))
587-
588-
checkFilterPredicate(Literal("2018-03-18".date) === dateAttr, classOf[Eq[_]],
589-
resultFun("2018-03-18"))
590-
checkFilterPredicate(Literal("2018-03-18".date) <=> dateAttr, classOf[Eq[_]],
591-
resultFun("2018-03-18"))
592-
checkFilterPredicate(Literal("2018-03-19".date) > dateAttr, classOf[Lt[_]],
593-
resultFun("2018-03-18"))
594-
checkFilterPredicate(Literal("2018-03-20".date) < dateAttr, classOf[Gt[_]],
595-
resultFun("2018-03-21"))
596-
checkFilterPredicate(Literal("2018-03-18".date) >= dateAttr, classOf[LtEq[_]],
597-
resultFun("2018-03-18"))
598-
checkFilterPredicate(Literal("2018-03-21".date) <= dateAttr, classOf[GtEq[_]],
599-
resultFun("2018-03-21"))
600-
601-
checkFilterPredicate(!(dateAttr < "2018-03-21".date), classOf[GtEq[_]],
602-
resultFun("2018-03-21"))
603-
checkFilterPredicate(
604-
dateAttr < "2018-03-19".date || dateAttr > "2018-03-20".date,
605-
classOf[Operators.Or],
606-
Seq(Row(resultFun("2018-03-18")), Row(resultFun("2018-03-21"))))
607616
}
608617
}
609618
}
610619
}
611620

612621
test("filter pushdown - timestamp") {
613622
Seq(true, false).foreach { java8Api =>
614-
withSQLConf(
615-
SQLConf.DATETIME_JAVA8API_ENABLED.key -> java8Api.toString,
616-
SQLConf.LEGACY_PARQUET_REBASE_MODE_IN_WRITE.key -> "CORRECTED") {
617-
// spark.sql.parquet.outputTimestampType = TIMESTAMP_MILLIS
623+
Seq(CORRECTED, LEGACY).foreach { rebaseMode =>
618624
val millisData = Seq(
619625
"1000-06-14 08:28:53.123",
620626
"1582-06-15 08:28:53.001",
621627
"1900-06-16 08:28:53.0",
622628
"2018-06-17 08:28:53.999")
623-
withSQLConf(SQLConf.PARQUET_OUTPUT_TIMESTAMP_TYPE.key ->
624-
ParquetOutputTimestampType.TIMESTAMP_MILLIS.toString) {
629+
withSQLConf(
630+
SQLConf.DATETIME_JAVA8API_ENABLED.key -> java8Api.toString,
631+
SQLConf.LEGACY_PARQUET_REBASE_MODE_IN_WRITE.key -> rebaseMode.toString,
632+
SQLConf.PARQUET_OUTPUT_TIMESTAMP_TYPE.key -> TIMESTAMP_MILLIS.toString) {
625633
testTimestampPushdown(millisData, java8Api)
626634
}
627635

628-
// spark.sql.parquet.outputTimestampType = TIMESTAMP_MICROS
629636
val microsData = Seq(
630637
"1000-06-14 08:28:53.123456",
631638
"1582-06-15 08:28:53.123456",
632639
"1900-06-16 08:28:53.123456",
633640
"2018-06-17 08:28:53.123456")
634-
withSQLConf(SQLConf.PARQUET_OUTPUT_TIMESTAMP_TYPE.key ->
635-
ParquetOutputTimestampType.TIMESTAMP_MICROS.toString) {
641+
withSQLConf(
642+
SQLConf.DATETIME_JAVA8API_ENABLED.key -> java8Api.toString,
643+
SQLConf.LEGACY_PARQUET_REBASE_MODE_IN_WRITE.key -> rebaseMode.toString,
644+
SQLConf.PARQUET_OUTPUT_TIMESTAMP_TYPE.key -> TIMESTAMP_MICROS.toString) {
636645
testTimestampPushdown(microsData, java8Api)
637646
}
638647

639-
// spark.sql.parquet.outputTimestampType = INT96 doesn't support pushdown
640-
withSQLConf(SQLConf.PARQUET_OUTPUT_TIMESTAMP_TYPE.key ->
641-
ParquetOutputTimestampType.INT96.toString) {
648+
// INT96 doesn't support pushdown
649+
withSQLConf(
650+
SQLConf.DATETIME_JAVA8API_ENABLED.key -> java8Api.toString,
651+
SQLConf.PARQUET_OUTPUT_TIMESTAMP_TYPE.key -> INT96.toString) {
642652
import testImplicits._
643653
withTempPath { file =>
644654
millisData.map(i => Tuple1(Timestamp.valueOf(i))).toDF

0 commit comments

Comments
 (0)