zhf999 opened a new pull request, #172:
URL: https://github.com/apache/paimon-cpp/pull/172

   <!-- PR titles must follow Conventional Commits: <type>(<optional-scope>): 
<description> -->
   
   ### Purpose
   
   Reading a Parquet column of type `list<struct<... timestamp(us, <tz>)>>` (or 
a `map` whose value
   field is not literally named `value`) failed with:
   
   ```
   Parquet does not support partial projection inside list/map: src ... vs 
target ...
   ```
   
   even though no partial projection was requested.
   
   `ParquetFileBatchReader::CollectLeafIndices()` guards against pruning fields 
inside LIST/MAP by
   requiring `read_type->Equals(file_type)`. That guard is too strict, because 
the two types being
   compared come from different layers:
   
   - `file_type` comes from `FileReaderWrapper::GetSchema()`, i.e. the raw 
Arrow schema derived from
     the Parquet metadata.
   - `read_type` is the Paimon-facing read schema. Even the default self-read 
path in
     `ParquetFileBatchReader::Create()` feeds back `GetFileSchema()`, which has 
already been rewritten
     by `ParquetTimestampConverter::AdjustTimezone()`.
   
   So `Equals()` rejects pure representation differences that the reader is 
designed to reconcile
   later in `NextBatch()` via 
`ParquetTimestampConverter::NeedCastArrayForTimestamp()` /
   `CastArrayForTimestamp()`:
   
   1. **Timezone**: the Parquet reader always reports LTZ timestamps as `UTC`, 
while Paimon exposes
      them in the local timezone.
   2. **Unit**: the Parquet writer has no SECOND timestamp, so a SECOND column 
is stored as MILLI and
      cast back to SECOND on read.
   3. **Child field names / nullability**: a map value field named `attrs` in 
the file versus the
      `value` produced by `arrow::map()`, a `list` element field name, or a 
differing nullable flag.
   
   This PR makes the guard compare only the *projection shape*:
   
   - New file-local helper `HasSameNestedProjectionShape()` in
     `src/paimon/format/parquet/parquet_file_batch_reader.cpp`:
     - STRUCT: same field count, same field names, recursively same shape 
(nullability ignored).
     - LIST: compares `value_type()` only, ignoring the element field name and 
nullability.
     - MAP: compares `key_type()` and `item_type()` only, ignoring the 
key/value field names.
     - TIMESTAMP leaves: accepted when the units are equal, or when the file is 
MILLI and the read
       type is SECOND — exactly the conversions `ParquetTimestampConverter` 
supports. Timezone
       differences are always accepted and left to the cast path.
     - All other leaves keep the strict `Equals()` check, and a 
nested-versus-atomic mismatch is
       rejected.
   - `CollectLeafIndices()` splits the shared LIST/MAP branch and now recurses 
through
     `ListType::value_type()` and `MapType::key_type()`/`item_type()` instead 
of iterating
     `file_type->field(i)`. This also fixes a latent leaf-index bug: 
`arrow::MapType::num_fields()`
     is 1 (the `entries` struct), so the old code fell into the STRUCT branch 
and matched map children
     **by name**; when the file's value field name differed from the read 
schema's, it took the
     `SkipLeafIndices()` path and silently produced a wrong leaf index list.
   
   Fail-fast behavior for genuinely unsupported cases is preserved: pruning a 
field from a struct
   inside a list/map, incompatible leaf types, and unsupported timestamp unit 
conversions all still
   return `Status::Invalid`.
   
   ### Tests
   
   New case in target `paimon-parquet-format-test`:
   
   - `ParquetFileBatchReaderTest.TestNestedListTimestampTimezoneAndMapFieldName`
     (`src/paimon/format/parquet/parquet_file_batch_reader_test.cpp`) writes
     `list<struct<key: utf8, attrs: map<utf8, utf8 ("attrs" value field)>, 
updated_at: timestamp(us,
     Asia/Shanghai)>>` under a `TimezoneGuard`, including empty maps, null maps 
and null timestamps,
     then asserts:
     - the full round trip now succeeds and matches the expected array 
(previously `Status::Invalid`);
     - pruning `updated_at` from the nested struct still fails with
       `"Parquet does not support partial projection inside list/map"`;
     - reading `updated_at` as `utf8` still fails;
     - reading `updated_at` as `timestamp(ns, ...)` (an unsupported unit 
conversion) still fails.
   
   ### API and Format
   
   No changes.
   
   ### Documentation
   
   No changes.
   
   ### Generative AI tooling
   
   No.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to