fix: handle param overlap between model and its dotted child - #1901
fix: handle param overlap between model and its dotted child#1901ilongin wants to merge 8 commits into
Conversation
When map() params (or to_iter columns) include both a model and one of its dotted children (e.g. params=["fr.name", "fr"]), row_to_objs walked a linear pos counter over row values whose columns had already been deduped by to_udf_spec, causing IndexError. Same latent shape in row_to_features. Both now look up positions by db-flat name.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Fixes parent/child parameter overlap during row hydration.
Changes:
- Replaces positional unpacking with DB-column name lookups.
- Adds shared model sub-row assembly with optional-model sentinel support.
- Adds unit and end-to-end overlap tests.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/datachain/lib/signal_schema.py |
Implements name-based row hydration. |
tests/unit/lib/test_signal_schema.py |
Tests overlapping schema values. |
tests/unit/lib/test_datachain.py |
Tests .map() and to_iter() behavior. |
Suppressed comments (1)
src/datachain/lib/signal_schema.py:765
- This recomputes
db_signals()and rebuilds the positions dictionary for every yielded row, althoughto_iter()already computes the same DB signal list once before entering its row loop. On large iterations this adds a full schema traversal and dictionary allocation per record; compute the mapping once per schema/iterator and reuse it.
positions: dict[str, int] = {
str(name): i for i, name in enumerate(self.db_signals())
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Deploying datachain with
|
| Latest commit: |
a63f6de
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://19390d81.datachain-2g6.pages.dev |
| Branch Preview URL: | https://ilongin-1883-fix-param-overl.datachain-2g6.pages.dev |
| return res | ||
|
|
||
| @cached_property | ||
| def _udf_row_positions(self) -> dict[str, int]: |
There was a problem hiding this comment.
AI generated as an attempt to simplify this:
_parse_row() already has a RowDict keyed by these flattened DB names, so converting it to a list and then reconstructing name→position caches seems avoidable. Could we pass that mapping directly to row_to_objs() and gather model subrows by their ordered DB column names? For sequence compatibility, row_to_objs() could normalize sequences with dict(zip(self.to_udf_spec(), row, strict=True)).
This would remove the four position caches and _model_row_positions(), while leaving row_to_features() and _hydrate_model() unchanged. Feature rows retain duplicate columns and already handle parent/child overlap correctly; the new to_iter/row_to_features overlap tests also pass on main.
shcheklein
left a comment
There was a problem hiding this comment.
Please check if we can simplify the implementation
Fixes #1883.
When
.map(params=[...])(orto_iter(...)) asked for both a model and one of its dotted children, e.g.params=["fr.name", "fr"], we hitIndexErrorwhen building the UDF input row.Root cause:
to_udf_specdedupes shared columns (dict keyed by db-flat name), so the row has one copy of the shared leaf.row_to_objswalked a linearposcounter and expected two copies.row_to_featureshas the same shape - not user-visible today becausedb_signalsdoesn't dedupe, but same fragile pattern.Both now build a
{db_name -> row_index}map and look up each value's columns by name.row_to_objs: positions built fromto_udf_spec().row_to_features: positions built fromdb_signals()(dupes still work - dict overwrite is a no-op since both indices hold the same column value)._sub_row_for_modelassembles the per-model sub-row, handling the_type_tagsentinel for nestedOptional[Model].Tests: unit tests for
row_to_objsandrow_to_featurescovering scalar/nested/optional overlap, plus e2e coverage via.map()andto_iter.