Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions core/src/main/resources/error/error-classes.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
],
"sqlState" : "22007"
},
"CANNOT_INFER_DATE_WITHOUT_INFER_SCHEMA" : {
"message" : [
"Cannot infer date when schema inference is disabled."
],
"sqlState" : "22007"
},
"CANNOT_PARSE_DECIMAL" : {
"message" : [
"Cannot parse decimal"
Expand Down
2 changes: 1 addition & 1 deletion docs/sql-data-sources-csv.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Data source options of CSV can be set via:
<td>read</td>
</tr>
<tr>
<td><code>inferDate</code></td>
<td><code>preferDate</code></td>
<td>false</td>
<td>Whether or not to infer columns that satisfy the <code>dateFormat</code> option as <code>Date</code>. Requires <code>inferSchema</code> to be <code>true</code>. When <code>false</code>, columns with dates will be inferred as <code>String</code> (or as <code>Timestamp</code> if it fits the <code>timestampFormat</code>).</td>
Comment thread
HyukjinKwon marked this conversation as resolved.
Outdated
<td>read</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ class CSVInferSchema(val options: CSVOptions) extends Serializable {
case _: DecimalType => tryParseDecimal(field)
case DoubleType => tryParseDouble(field)
case DateType => tryParseDateTime(field)
case TimestampNTZType if options.inferDate => tryParseDateTime(field)
case TimestampNTZType if options.preferDate => tryParseDateTime(field)
case TimestampNTZType => tryParseTimestampNTZ(field)
case TimestampType if options.inferDate => tryParseDateTime(field)
case TimestampType if options.preferDate => tryParseDateTime(field)
case TimestampType => tryParseTimestamp(field)
case BooleanType => tryParseBoolean(field)
case StringType => StringType
Expand Down Expand Up @@ -178,7 +178,7 @@ class CSVInferSchema(val options: CSVOptions) extends Serializable {
private def tryParseDouble(field: String): DataType = {
if ((allCatch opt field.toDouble).isDefined || isInfOrNan(field)) {
DoubleType
} else if (options.inferDate) {
} else if (options.preferDate) {
tryParseDateTime(field)
} else {
tryParseTimestampNTZ(field)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,24 @@ class CSVOptions(
* Disabled by default for backwards compatibility and performance. When enabled, date entries in
* timestamp columns will be cast to timestamp upon parsing. Not compatible with
* legacyTimeParserPolicy == LEGACY since legacy date parser will accept extra trailing characters
*
* The flag is only enabled if inferSchema is set to true.
*/
val inferDate = {
val inferDateFlag = getBool("inferDate")
if (SQLConf.get.legacyTimeParserPolicy == LegacyBehaviorPolicy.LEGACY && inferDateFlag) {
val preferDate = {
val preferDateFlag = getBool("preferDate")
if (preferDateFlag && SQLConf.get.legacyTimeParserPolicy == LegacyBehaviorPolicy.LEGACY) {
throw QueryExecutionErrors.inferDateWithLegacyTimeParserError()
}
inferDateFlag
if (preferDateFlag && !inferSchemaFlag) {
Comment thread
HyukjinKwon marked this conversation as resolved.
Outdated
throw QueryExecutionErrors.inferDateWithoutInferSchemaError()
}
preferDateFlag
}

// Provide a default value for dateFormatInRead when inferDate. This ensures that the
// Provide a default value for dateFormatInRead when preferDate. This ensures that the
// Iso8601DateFormatter (with strict date parsing) is used for date inference
val dateFormatInRead: Option[String] =
if (inferDate) {
if (preferDate) {
Option(parameters.getOrElse("dateFormat", DateFormatter.defaultPattern))
} else {
parameters.get("dateFormat")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ class UnivocityParser(
} catch {
case NonFatal(e) =>
// There may be date type entries in timestamp column due to schema inference
if (options.inferDate) {
if (options.preferDate) {
daysToMicros(dateFormatter.parse(datum), options.zoneId)
} else {
// If fails to parse, then tries the way used in 2.0 and 1.x for backwards
Expand All @@ -254,7 +254,7 @@ class UnivocityParser(
try {
timestampNTZFormatter.parseWithoutTimeZone(datum, false)
} catch {
case NonFatal(e) if (options.inferDate) =>
case NonFatal(e) if options.preferDate =>
daysToMicros(dateFormatter.parse(datum), TimeZoneUTC.toZoneId)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,12 @@ private[sql] object QueryExecutionErrors extends QueryErrorsBase {
)
}

def inferDateWithoutInferSchemaError(): Throwable with SparkThrowable = {
new SparkIllegalArgumentException(errorClass = "CANNOT_INFER_DATE_WITHOUT_INFER_SCHEMA",
messageParameters = Array()
)
}

def streamedOperatorUnsupportedByDataSourceError(
className: String, operator: String): Throwable = {
new UnsupportedOperationException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,30 +201,30 @@ class CSVInferSchemaSuite extends SparkFunSuite with SQLHelper {

test("SPARK-39469: inferring date type") {
// "yyyy/MM/dd" format
var options = new CSVOptions(Map("dateFormat" -> "yyyy/MM/dd", "inferDate" -> "true"),
var options = new CSVOptions(Map("dateFormat" -> "yyyy/MM/dd", "preferDate" -> "true"),
false, "UTC")
var inferSchema = new CSVInferSchema(options)
assert(inferSchema.inferField(NullType, "2018/12/02") == DateType)
// "MMM yyyy" format
options = new CSVOptions(Map("dateFormat" -> "MMM yyyy", "inferDate" -> "true"),
options = new CSVOptions(Map("dateFormat" -> "MMM yyyy", "preferDate" -> "true"),
false, "GMT")
inferSchema = new CSVInferSchema(options)
assert(inferSchema.inferField(NullType, "Dec 2018") == DateType)
// Field should strictly match date format to infer as date
options = new CSVOptions(
Map("dateFormat" -> "yyyy-MM-dd", "timestampFormat" -> "yyyy-MM-dd'T'HH:mm:ss",
"inferDate" -> "true"),
"preferDate" -> "true"),
columnPruning = false,
defaultTimeZoneId = "GMT")
inferSchema = new CSVInferSchema(options)
assert(inferSchema.inferField(NullType, "2018-12-03T11:00:00") == TimestampType)
assert(inferSchema.inferField(NullType, "2018-12-03") == DateType)
}

test("SPARK-39469: inferring date and timestamp types in a mixed column with inferDate=true") {
test("SPARK-39469: inferring date and timestamp types in a mixed column with preferDate=true") {
var options = new CSVOptions(
Map("dateFormat" -> "yyyy_MM_dd", "timestampFormat" -> "yyyy|MM|dd",
"timestampNTZFormat" -> "yyyy/MM/dd", "inferDate" -> "true"),
"timestampNTZFormat" -> "yyyy/MM/dd", "preferDate" -> "true"),
columnPruning = false,
defaultTimeZoneId = "UTC")
var inferSchema = new CSVInferSchema(options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,10 +373,10 @@ class UnivocityParserSuite extends SparkFunSuite with SQLHelper {
assert(err.getMessage.contains("Illegal pattern character: n"))
}

test("SPARK-39469: dates should be parsed correctly in a timestamp column when inferDate=true") {
test("SPARK-39469: dates should be parsed correctly in a timestamp column when preferDate=true") {
def checkDate(dataType: DataType): Unit = {
val timestampsOptions =
new CSVOptions(Map("inferDate" -> "true", "timestampFormat" -> "dd/MM/yyyy HH:mm",
new CSVOptions(Map("preferDate" -> "true", "timestampFormat" -> "dd/MM/yyyy HH:mm",
"timestampNTZFormat" -> "dd-MM-yyyy HH:mm", "dateFormat" -> "dd_MM_yyyy"),
false, DateTimeUtils.getZoneId("-08:00").toString)
// Use CSVOption ZoneId="-08:00" (PST) to test that Dates in TimestampNTZ column are always
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2797,13 +2797,13 @@ abstract class CSVSuite
"inferSchema" -> "true",
"timestampFormat" -> "yyyy-MM-dd'T'HH:mm:ss",
"dateFormat" -> "yyyy-MM-dd",
"inferDate" -> "true")
"preferDate" -> "true")
val options2 = Map(
"header" -> "true",
"inferSchema" -> "true",
"inferDate" -> "true")
"preferDate" -> "true")

// Error should be thrown when attempting to inferDate with Legacy parser
// Error should be thrown when attempting to preferDate with Legacy parser
if (SQLConf.get.legacyTimeParserPolicy == LegacyBehaviorPolicy.LEGACY) {
val msg = intercept[IllegalArgumentException] {
spark.read
Expand Down Expand Up @@ -2840,6 +2840,17 @@ abstract class CSVSuite
}
}

test("SPARK-39904: Fail to prefer dates if inferSchema=false") {
val msg = intercept[IllegalArgumentException] {
spark.read
.format("csv")
.option("inferSchema", "false")
.option("preferDate", "true")
.load(testFile(dateInferSchemaFile))
}.getMessage
assert(msg.contains("CANNOT_INFER_DATE_WITHOUT_INFER_SCHEMA"))
}

test("SPARK-39731: Correctly parse dates and timestamps with yyyyMMdd pattern") {
withTempPath { path =>
Seq(
Expand Down