Skip to content

Native Parquet scan skips Spark's type check on a root field that loses its file column to a field-id match #7207

Open

Description

@andygrove

Describe the bug

With spark.sql.parquet.fieldId.read.enabled=true, the native Parquet scan never checks the requested type of a root field whose file column another requested field claims by id. Spark checks it and fails the read.

Take a file with A (id 1, BIGINT) = 7, read with the schema [x (id 1) BIGINT, a (no id) STRUCT<v BIGINT>]. Spark's clipParquetGroupFields resolves each requested field on its own, so x selects A by id, a selects A by name, and clipping the primitive A against the struct a fails with ClassCastException: optional int64 A = 1 is not a group in both Spark readers. Comet gives A to x, the stronger claim, and reads a as a missing column, so it returns [7, null].

A primitive with a type that Spark can't read the column as behaves the same way. With a INT over A BIGINT, Spark's vectorized reader fails with SchemaColumnConvertNotSupportedException once it decodes a row group, because VectorizedColumnReader.readBatch calls getUpdater for every requested leaf. parquet-mr returns NULL, and Comet returns NULL.

Inside a struct, Comet raises both errors on main.

Steps to reproduce

In a CometTestBase suite on the default Spark 4.1.3 profile:

def withId(id: Int) =
  new MetadataBuilder().putLong(ParquetUtils.FIELD_ID_METADATA_KEY, id.toLong).build()
withSQLConf(SQLConf.PARQUET_FIELD_ID_WRITE_ENABLED.key -> "true") {
  spark
    .createDataFrame(
      spark.sparkContext.parallelize(Seq(Row(7L))),
      new StructType().add("A", LongType, true, withId(1)))
    .write
    .parquet(path)
}
val readSchema = new StructType()
  .add("x", LongType, true, withId(1))
  .add("a", new StructType().add("v", LongType), true)
withSQLConf(SQLConf.PARQUET_FIELD_ID_READ_ENABLED.key -> "true") {
  spark.read.schema(readSchema).parquet(path).collect()
}

On main at 330b29c:

Read schema Spark vectorized Spark parquet-mr Comet native scan
[x id1 bigint, a struct<v bigint>] ClassCastException: optional int64 A = 1 is not a group the same [7, null]
[a struct<v bigint>, x id1 bigint] the same the same [7, null]
[x id1 bigint, a array<struct<v bigint>>] the same the same [7, null]
[x id1 bigint, a int] SchemaColumnConvertNotSupportedException: column: [A], physicalType: INT64, logicalType: int [7, null] [7, null]
s struct<x id1 bigint, a struct<v bigint>> ClassCastException ClassCastException SchemaColumnConvertNotSupportedException at [s, A]

Expected behavior

Comet should check a losing root field against the file column it selects, as it does for a nested field: fail at file open where Spark's clipping fails, and defer a type rejection to a non-empty row group the way RejectOnNonEmpty does.

Additional context

Found while reviewing #7196, which keeps the struct check for a nested field that loses its claim. The root reads such a field as missing before any type check runs. branch-1.2 has the same code as main here.

The shape needs field-id reads and a read schema in which a field without an id has the name of a file column that another field selects by id, so it should be rare in practice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:scanParquet scan / data readingbugSomething isn't workingpriority:lowMinor issues, test failures, tooling, cosmetic

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions