[python] Support selected-key projection for shared-shredding MAP - #9789
[python] Support selected-key projection for shared-shredding MAP#9789XiaoHongbo-Hope wants to merge 7 commits into
Conversation
|
@JingsongLi Could you please review the public API choice? PyPaimon has no expression layer, so this PR keeps the existing projection entry point and uses Spark-style MAP access: read_builder.with_projection([
"profile.name",
"attributes['key.with.dots']",
])We considered adding |
|
|
||
| ```python | ||
| read_builder = read_builder.with_projection([ | ||
| 'id', "attributes['key.with.dots']" |
There was a problem hiding this comment.
...the doc is super strange, you should introduce a simple one. And explain key with dots...
1a69325 to
e12cb2c
Compare
e12cb2c to
12cdaaa
Compare
JingsongLi
left a comment
There was a problem hiding this comment.
Two correctness issues in projection resolution and the fallback for ROW files, both reproduced with end-to-end table reads.
| ] | ||
| if not candidates: | ||
| continue | ||
| top = max(candidates, key=len) |
There was a problem hiding this comment.
[P2] Preserve the existing ROW-path resolution priority
Choosing the longest top-level ROW prefix changes the meaning of an already valid projection. For example, with top-level fields a: ROW<b: ROW<c: BIGINT>>, a.b: ROW<d: BIGINT>, and id, with_projection(['a.b.c', 'id']) previously returned a_b_c and id. This code selects the top-level a.b first, fails to find its child c, and silently drops the projection instead of resolving a -> b -> c. If the top-level a.b also has a child c, the same projection silently reads a different column. I reproduced both cases against the base and PR implementations using actual table writes and reads.
Could we preserve the original valid ROW path first, and only try dotted top-level prefixes when that path cannot be resolved? Exact top-level name matches can still retain their existing precedence.
| if path[1] not in keys: | ||
| keys.append(path[1]) | ||
| try: | ||
| field = map_selected_keys_field(field, keys) |
There was a problem hiding this comment.
[P2] Preserve the full-MAP fallback when the physical reader is ROW
This replaces the MAP read type with a selected-key ROW before the physical format is known, but FormatRowReader still returns a MAP array. DataFileBatchReader then raises ArrowNotImplementedError: Unsupported cast from map<string, int64> to struct using function cast_struct.
This also affects the explicitly supported Parquet data-evolution path: enable row-tracking.enabled, data-evolution.enabled, and data-evolution.row-sidecar.enabled, write 100 rows with attributes = {'first': row_id}, and read _ROW_ID = 5 while projecting only attributes['first']. The sparse read selects the ROW sidecar and fails. Projecting both the complete attributes MAP and the key succeeds and returns 5; a table using file.format = row exhibits the same failure.
Please retain the complete-MAP fallback for readers that do not assemble selected-key ROWs, or add that conversion to the ROW reader. Checking only the table's file.format would miss Parquet tables that select a ROW sidecar.
There was a problem hiding this comment.
The ROW and row-sidecar cases now pass. I think key projection should support MAP<STRING, BLOB> as well, since this is an existing supported table type.
There is still a failure on 88cafac: create a data-evolution table with payload MAP<STRING, BLOB> and write {'k': b'hello', 'v': b'world'}. Reading the complete MAP succeeds, but with_projection(["payload['k']"]) raises ArrowTypeError. Projecting both the complete MAP and the key also succeeds.
The selected-key ROW type reaches FormatBlobReader, so it no longer recognizes the field as a MAP of BLOBs and tries to read it as a scalar BLOB. The failure happens before the new conversion in DataFileBatchReader. Please preserve the physical MAP schema for BLOB decoding and then extract the selected key, or use the complete-MAP fallback for this layout.
Please also add an end-to-end regression test that writes a real MAP-of-BLOBs table and verifies key-only projection returns the expected bytes. Cover missing keys, null values, and null/empty MAPs, and compare the results with complete-MAP reads so the test exercises the actual BLOB reader path.
Purpose
Project literal keys from top-level
MAP<STRING, ...>columns through the existing API:Nested
ROWpaths remainprofile.name.attributes['sub.key']treatssub.keyas one literal MAP key.Behavior
Scope
This PR only adds projection. Physical-column filters continue to work. Filtering projected MAP keys remains rejected. Query authorization also remains rejected because parent-MAP filters and masks must run before key extraction.
Tests
Focused current-PyArrow and PyArrow 6 tests, Flake8, and
git diff --checkpass.