-
Notifications
You must be signed in to change notification settings - Fork 23
fix!: apply ReadRel projection during schema inference #266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -509,7 +509,10 @@ def to_id_based_outer_references(plan: stplan.Plan) -> stplan.Plan: | |||||||||||
| This is the shared-subtree / DAG case that offset-based ``steps_out`` cannot | ||||||||||||
| address unambiguously. | ||||||||||||
| * a ``post_join_filter``, or a leaf host's own filter, exposes the **host's** | ||||||||||||
| output row, so the host is anchored. | ||||||||||||
| output row, so the host is anchored, except for projected reads below. | ||||||||||||
| * a ``ReadRel``'s ``filter`` / ``best_effort_filter`` uses its base schema. | ||||||||||||
| When the read has a projection, its output need not carry that row, so | ||||||||||||
| references into these filters remain offset-based. | ||||||||||||
| * a join *condition* / ``residual_expression`` exposes the **combined** left+right | ||||||||||||
| row; the join's own output equals that row for a non-reducing join, so the join | ||||||||||||
| is anchored. For a *reducing* join (semi/anti) the two differ and no relation | ||||||||||||
|
|
@@ -584,11 +587,11 @@ def convert_expr(expr, scope, binding): | |||||||||||
| f"{len(scope)} enclosing query scope(s)" | ||||||||||||
| ) | ||||||||||||
| target = scope[-steps] | ||||||||||||
| # None marks a combined-inputs scope with no anchorable relation | ||||||||||||
| # (a reducing join's condition). A lateral join's rel_anchor is | ||||||||||||
| # reserved for its right input's left-row reference, so it cannot | ||||||||||||
| # double as the output-row anchor a correlation here would need. | ||||||||||||
| # Both are left offset-based (spec-valid, read by inference). | ||||||||||||
| # None marks a scope with no anchorable relation, such as a | ||||||||||||
| # reducing join's condition or a projected read's filter. | ||||||||||||
| # A lateral join's rel_anchor is reserved for its right input's | ||||||||||||
| # left-row reference, so it cannot double as an output anchor. | ||||||||||||
| # These references stay offset-based. | ||||||||||||
| if target is not None and not _binding_is_lateral_join(target): | ||||||||||||
| oref.rel_reference = anchor_for(target) | ||||||||||||
| elif rex == "subquery": | ||||||||||||
|
|
@@ -604,15 +607,18 @@ def convert_rel(rel, scope): | |||||||||||
| if node is not None: | ||||||||||||
| # The relation whose output row a subquery here would see one level up: | ||||||||||||
| # a single-input host exposes its input; a leaf or multi-input host its | ||||||||||||
| # own output -- except a reducing join's combined-inputs-scoped fields, | ||||||||||||
| # whose scope no relation's output carries (binding None -> left as-is). | ||||||||||||
| # own output. Join conditions and projected read filters may use a | ||||||||||||
| # different row with no relation to anchor (binding None -> left as-is). | ||||||||||||
| single_input = _child_rel(*children[0]) if len(children) == 1 else None | ||||||||||||
| reducing = single_input is None and _is_reducing_join(node) | ||||||||||||
| projected_read = rel_type == "read" and node.HasField("projection") | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Include
Suggested change
|
||||||||||||
| for name, expr in _iter_named_direct_expressions(node): | ||||||||||||
| if single_input is not None: | ||||||||||||
| binding = single_input | ||||||||||||
| elif reducing and name in _JOIN_COMBINED_SCOPED_FIELDS: | ||||||||||||
| binding = None | ||||||||||||
| elif projected_read and name in ("filter", "best_effort_filter"): | ||||||||||||
| binding = None | ||||||||||||
| else: | ||||||||||||
| binding = rel | ||||||||||||
| convert_expr(expr, scope, binding) | ||||||||||||
|
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gate this on
projection.HasField("select"). AMaskExpressioncan be present withselectunset — assigningmaintain_singular_structalone is enough to mark the parent present — and the mask then selects nothing, so the read collapses to zero fields where it previously returned the full base schema. The spec makes an absent projection default to all of schema, never to none.