Skip to content

Fix accessing an inherited property through subcollection - #580

Merged
john-sanchez31 merged 18 commits into
mainfrom
John/fix_access_calc
Oct 13, 2026
Merged

john-sanchez31 merged 18 commits into
mainfrom
John/fix_access_calc

Conversation

@john-sanchez31

@john-sanchez31 john-sanchez31 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Linked ticket

Closes #564

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Docs / config

What changed and why?

Fix for the bug where a subcollection accessing a inherited term qualify correctly but later raises an error. Adding a new verification on qualification that makes sure that the reference being qualified is actually part of the subcollection where is being accessed from.

How I tested this?

  • Added the regression test parent_calc_access_error that raises the corresponding error
  • Added the test shadowed_calc_term_exe that covers the possibility of a name being shadow in the child which makes its access valid.

Notes for reviewers

Adding better error handling for the mock server setup

@john-sanchez31 john-sanchez31 self-assigned this Sep 30, 2026
@john-sanchez31
john-sanchez31 marked this pull request as ready for review October 5, 2026 16:31
@john-sanchez31 john-sanchez31 changed the title Fix wrong access handling for ancestor property Fix accessing an inherited property through subcollection Oct 5, 2026
Comment thread tests/conftest.py

# Wait until server is ready
for _ in range(20):
if proc.poll() is not None:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated changes to the mock server setup error handling. I was already using port 8000 but didn't get the correct error message

@hadia206 hadia206 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good investigation and solution John.
I have minor comments that would like to get addressed before approving

Comment thread tests/conftest.py Outdated
Comment on lines +3225 to +3226
output, _ = proc.communicate()
raise RuntimeError(f"Mock server failed to start:\n{output}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in try/except with a timeout to avoid hanging.

try:
            output, _ = proc.communicate(timeout=5)
except subprocess.TimeoutExpired:
            proc.kill()
            output, _ = proc.communicate()
raise RuntimeError(f"Mock server failed to start:\n{output}")

.WHERE(key <= 3)
)


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add another test for PARTITION-child with context_inherits being False as written in the comments in the code.

        Accesses are still valid when the term comes from the child's own
        data, e.g. a CALCULATE inside the child expression
        (`customer.CALCULATE(customer_name=name).orders.customer_name`) or
        inside the data of a PARTITION child (`pricing_collection.price`).

e.g. something like the pricing_collection.price case mentioned above. Right now only the "shadowed" and "plain error" branches are tested.

@knassre-bodo knassre-bodo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have one structural change I'd prefer to how the names are found.

Comment thread pydough/unqualified/qualification.py Outdated
Comment on lines +660 to +683
def names_defined_in_child_chain(self, unqualified: UnqualifiedNode) -> set[str]:
"""
Walks the unqualified collection chain of a child access (e.g.
`customer.WHERE(...).CALCULATE(customer_name=name).orders.BEST(...)`)
down to the root, collecting every term name defined by a CALCULATE
that is part of the chain itself.

Args:
`unqualified`: the unqualified collection node to walk.

Returns:
The set of term names defined by CALCULATEs within the chain.
"""
defined: set[str] = set()
node: UnqualifiedNode = unqualified
while not isinstance(node, UnqualifiedRoot):
if isinstance(node, UnqualifiedCalculate):
for term_name, _ in node._parcel[1]:
defined.add(term_name)
predecessor = node._parcel[0]
if not isinstance(predecessor, UnqualifiedNode):
break
node = predecessor
return defined

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a more dangerous/complex way to handle the search for legal names. I think a version that traverses the QDAG nodes would be safer, since those APIs are more well defined for this sort of interaction.

@knassre-bodo knassre-bodo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just need to resolve merge conflicts, re-run CI, then go ahead and merge 👍

@john-sanchez31
john-sanchez31 merged commit 7418af1 into main Oct 13, 2026
36 of 39 checks passed
@john-sanchez31
john-sanchez31 deleted the John/fix_access_calc branch October 13, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Context does not contain expression $0.<expr> when accessing parent CALCULATE fields via relationship prefix

3 participants