Repository navigation
Conversation
Spark's vectorized reader has no updater that reads an INT96 or INT64 timestamp column as a string, a narrower integer, a float or a decimal, or an INT96 column as bigint, so it fails with a column type mismatch. The native scan cast the values instead. Reject these conversions the same way as the other getUpdater rejections. Closes apache#7214.
comphead
left a comment
There was a problem hiding this comment.
I checked the new rules against ParquetVectorUpdaterFactory in 3.4.3, 3.5.9, 4.0.4 and 4.1.3. An INT64 timestamp has only the long, timestamp and day-time interval updaters, and isDecimalTypeMatched refuses a timestamp annotation, so the rejected targets match. One point on where the rule lives, which also covers a DATE gap of the same kind.
rich7420
left a comment
There was a problem hiding this comment.
@ump45nose, thanks for the patch.
| | DataType::Int32 | ||
| | DataType::Float32 | ||
| | DataType::Float64 | ||
| | DataType::Decimal128(_, _) |
There was a problem hiding this comment.
ArrowWriter stores UTC timestamp seconds as unannotated INT64. Spark accepts decimal(20,0) reads. Validate footer and runtime paths against Parquet types.
There was a problem hiding this comment.
Good catch — addressed in 5714377.
ArrowWriter writes Timestamp(Second, _) as unannotated INT64 (Parquet has no second unit), and an ARROW:schema hint can restore Timestamp. Spark still reads the Parquet type and accepts decimal(20,0) under the INT64 canReadAsDecimal rule.
Changes:
Timestamp → Decimal*rejection now applies only to annotated units (millis/micros/nanos);Timestamp(Second, _)follows the INT64 precision floor (>= 20).- Footer
mark_parquet_leavesrewrites an ARROW-restored Timestamp over unannotated INT64 back toInt64, so the conversion check keys on the Parquet type. - Unit test covers Second→
decimal(20,0)accept /decimal(19,0)reject, micros still rejected, and Int64 after rewrite; runtime roundtrip via ArrowWriter Second+UTC →decimal(20,0)checks the values.
(AI-assisted reply.)
ArrowWriter stores Timestamp(Second) as unannotated INT64 because Parquet has no second unit; an ARROW:schema hint can restore Timestamp. Spark still reads the Parquet type and accepts decimal(20,0). Key Timestamp→Decimal rejection on annotated units only, apply the INT64 canReadAsDecimal rule to Second, and rewrite ARROW-restored Timestamp over unannotated INT64 to Int64 in the footer mark path.
Which issue does this PR close?
Closes #7214.
Rationale for this change
Spark's vectorized reader has no updater for these reads and fails with
PARQUET_COLUMN_DATA_TYPE_MISMATCH, but the native scan casts the values instead:string,byte/short/int,float/doubleordecimal(returns text, raw numbers or NULL);bigint(returns microseconds since the epoch). An INT64 timestamp read asbigintis fine in Spark and stays allowed.string,floatordecimal(returns text or numbers). Spark's INT32 case has no string or float branch, andisDecimalTypeMatchedrefuses a date annotation. Reading a DATE asintstays allowed. This one isn't in Native Parquet scan reads timestamp columns as string, int, double or decimal, and INT96 as bigint, where Spark rejects them #7214; it came up in review.What changes are included in this PR?
spark_has_updaterrejects these reads through its existing rules:Date32andTimestampjoin the primitive types that can't be read as a string or binary, and the(Timestamp, Int8 | Int16 | Int32 | Float32 | Float64 | Decimal*)and(Date32, Float32 | Decimal*)pairs join theis_spark_rejected_conversiontable. These are deferred like the othergetUpdaterrejections (RejectOnNonEmpty).bigintrule needs the Parquet type. LikeTIMESTAMP(NANOS)in fix: reject TIMESTAMP(NANOS) Parquet columns read as timestamp #7089, the footer check (mark_parquet_leaves) now marks INT96 leaves with a privateINT96_TIMESTAMP_KEY, and files with INT96 leaves take the deferred check path even without row filters. To carry both markers, thenanos_timestamp: boolparameter became a smallParquetLeafenum (Other,NanosTimestamp,Int96).How are these changes tested?
parquet_timestamp_read_as_non_timestamp_errors: aTIMESTAMP(MICROS)column, with and without a timezone, read asstring,int,doubleanddecimal(20,0)fails with the Spark-shaped error; read asbigintit still works.parquet_date_read_as_string_float_or_decimal_errors: a DATE column read asstring,floatanddecimal(20,0)fails with the Spark-shaped error; read asintit is still accepted.int96_read_as_bigint_errors: an INT96 file read asbigintfails, with row filters on and off.int96_bigint_rejection_keys_on_the_parquet_type: a marked INT96 leaf is rejected asbigintand still accepted astimestamp; an unmarked timestamp leaf is still accepted asbigint.All of these fail without the change. I ran the
parquet::lib tests in thedatafusion-cometcrate (332 passed, 1 ignored) andcargo clippy -p datafusion-comet --all-targets -- -D warnings. In the full lib run, only the native UDF tests failed, because I hadn't builtcomet-test-udfs. I did not run the Spark/JVM suites.AI disclosure: I wrote the change, the tests and this description with help from an AI coding assistant. The part most worth checking is the INT96 marker, which uses the same footer-marking path as
TIMESTAMP(NANOS).