Commit 707c1b9
fix: extension-type metadata dropped for list-backed logical types (ITL-627) (#257)
* docs(specs): add ITL-627 design spec for list-backed extension type fixes
Root causes identified:
- ListLogicalType.get_polars_extension_type() omits metadata= arg, causing
Polars to export b'' on to_arrow() which _deserialize rejects
- SemanticHashingVisitor.visit_extension short-circuits on isinstance check
for generic aliases like list[File]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(specs): expand ITL-627 spec scope and add implementation plan
- Add Defect 3: MergeJoin drops extension type when aggregating logical-type columns
- Expand Defect 2 tests: set[File], list[list[T]], Dataclass with list[T] field
- Add list[File] x list[File] -> list[list[File]] MergeJoin case (was wrongly out of scope)
- Correct Fix 3: binary_output_schema needs no change (Schema stores Python types already correct)
- Add implementation plan with complete code for all 4 tasks
* fix(logical_types): pass metadata to make_polars_extension_type in ListLogicalType
ListLogicalType.get_polars_extension_type() was not passing the JSON metadata
bytes to make_polars_extension_type, so ext_metadata() returned None. Polars
exported b'' on to_arrow(), which _deserialize rejected with ValueError during
the Join/MergeJoin Polars round-trip. One-line fix covers both list[T] and set[T].
Fixes ITL-627 (Defect 1).
* test(operators): add regression tests for list extension column round-trip
Exercises the full Join and MergeJoin Polars round-trip with list[Path] extension
columns, confirming Fix 1 (ITL-627 Defect 1) at the operator integration level.
* test(operators): add data integrity assertions and clean up registration boilerplate
Keep registration boilerplate in both join tests and add a comment explaining
why it is required: ArrowTableStream does not trigger LogicalType registration,
so without explicit pa/pl registration Polars degrades the extension type to
its storage type during the operator's round-trip. Add row-count and value
spot-check assertions to verify data integrity after the join.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(operators): fix any->all in path value assertions
* fix(hashing): hash list-backed extension columns element-by-element
SemanticHashingVisitor.visit_extension previously short-circuited on
`not isinstance(python_type, type)` for list[File] (a GenericAlias),
returning the extension type unchanged — raw JSON path strings were
hashed instead of file contents.
Fix: detect list-backed extension types before the isinstance guard and
delegate to _visit_list_elements with a virtual large_list(elem_ext_type)
so each element is hashed identically to the scalar visit_extension path.
Handles set[T], list[list[T]], and arbitrary nesting depth via recursion.
Adds 9 regression tests covering: list[File], set[File], list[list[File]],
struct with list[File] field, passthrough when no handler, passthrough for
list[Path], content-change sensitivity, same-content determinism, and
element-wise hash symmetry with scalar File hashing.
* refactor(hashing): move method-body imports to module level; improve docstring
- Remove redundant from-body imports of File, pa, uuid, PythonTypeHandlerRegistry,
SemanticAwarePythonHasher in test_extension_type_hashing.py — all were already
available at module level.
- Add LogicalPath, ListLogicalType, PythonTypeHandlerRegistry, SemanticAwarePythonHasher
to module-level imports.
- Add idempotency note to _make_list_file_ext_type / _make_scalar_file_ext_type helpers.
- Add Args:/Returns: sections and passthrough-case documentation to
SemanticHashingVisitor.visit_extension docstring (Google style).
- Add defensive-guard comment on the `if args:` fallthrough.
* fix(operators): MergeJoin produces extension-typed list when merging logical-type columns
binary_static_process used pa.array(merged_vals) which inferred array type from
raw storage values, producing plain large_list(storage_type) and losing the
extension wrapper. Fix snapshots the element Arrow type before the Polars round-trip,
then builds the merged array as pa.ExtensionArray.from_storage(ListLogicalType, ...)
when the element is an extension type. Handles nested list[list[T]] naturally.
Fixes ITL-627 (Defect 3).
* refactor(operators): rename list_lt -> list_logical_type; add comments; improve test messages
- Rename list_lt to list_logical_type in binary_static_process for clarity.
- Add late-import comment explaining the circular dependency guard.
- Add failure messages to shape assertions in TestMergeJoinLogicalTypeColumns.
* fix(tests): use type-converter cache for list[Path] extension type in Join/MergeJoin regression tests
Fresh ListLogicalType instances create different underlying Arrow extension classes.
When two tests both call pa.register_extension_type() for the same extension name,
the second call is a no-op — so the second test holds a class object that is never
globally registered. Join's table.cast() then tries to cast between two different
class objects for the same extension name, raising ArrowTypeError.
Fix: use ctx.type_converter.register_python_class(list[Path]) which goes through
the type converter's shared cache, guaranteeing the same class object across tests.
* fix(hashing): address four review issues from brian-arnold
1. **Test contamination** (test_list_logical_type.py): Replace the fresh
`ListLogicalType(LogicalPath())` + manual `pa.register_extension_type` /
`pl.register_extension_type` with `ctx.type_converter.register_python_class(list[Path])`.
The manual approach registered a different class object than the orcapod
registry held, causing `ArrowTypeError: Casting from extension<list[orcapod.path]>
to different extension type` when tests ran in `test_logical_types` before
`test_core/operators` order.
2. **list[T] / set[T] hash collision** (visitors.py): The outer extension name was
not encoded in the result, so `extension<list[orcapod.file]>` and
`extension<set[orcapod.file]>` with identical contents produced identical hashes.
Fixed by folding `extension_type.extension_name` into the combined result,
mirroring the scalar path encoding.
3. **Two crash paths** (visitors.py): (a) `list[list[Path]]` — `is_nested_list_or_set`
committed to recursing before checking whether the innermost type has a handler,
causing `_visit_list_elements` to return `large_list(extension<...>)` which Arrow
rejects in array creation. (b) Empty/null inner list — `_visit_list_elements`
fell back to the element extension type when no non-null element was present,
latching an extension type in `_process_table_columns`. Fixed by making the
hash-vs-passthrough decision type-driven (unwrap nesting to innermost, check
handler) before visiting any rows, and discarding the returned list type from
`_visit_list_elements` (using `_`) since we return `(large_binary(), combined)`
regardless.
4. **Wrong context in MergeJoin** (merge_join.py): `get_default_context().type_converter`
bypassed the operator's actual context. Replaced with `left_stream.data_context.type_converter`,
hoisted above the colliding-keys loop, and removed the late import.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: agent-kurodo[bot] <268466204+agent-kurodo[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Edgar Y. Walker <eywalker@users.noreply.github.com>1 parent a19b814 commit 707c1b9
9 files changed
Lines changed: 1979 additions & 9 deletions
File tree
- src/orcapod
- core/operators
- hashing
- logical_types
- superpowers
- plans
- specs
- tests
- test_core/operators
- test_hashing
- test_logical_types
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
182 | 182 | | |
183 | 183 | | |
184 | 184 | | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
185 | 194 | | |
186 | 195 | | |
187 | 196 | | |
| |||
232 | 241 | | |
233 | 242 | | |
234 | 243 | | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
235 | 249 | | |
236 | 250 | | |
237 | 251 | | |
| |||
273 | 287 | | |
274 | 288 | | |
275 | 289 | | |
276 | | - | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
277 | 302 | | |
278 | 303 | | |
279 | 304 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
198 | 198 | | |
199 | 199 | | |
200 | 200 | | |
201 | | - | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
202 | 224 | | |
203 | 225 | | |
204 | 226 | | |
205 | 227 | | |
206 | 228 | | |
207 | 229 | | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
208 | 290 | | |
209 | 291 | | |
210 | 292 | | |
211 | 293 | | |
212 | 294 | | |
213 | | - | |
214 | | - | |
215 | | - | |
| 295 | + | |
216 | 296 | | |
217 | 297 | | |
218 | 298 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
| 153 | + | |
153 | 154 | | |
154 | 155 | | |
155 | 156 | | |
| |||
0 commit comments