|
28 | 28 |
|
29 | 29 | from __future__ import annotations |
30 | 30 |
|
31 | | -import contextvars |
32 | 31 | from itertools import combinations |
33 | 32 | from typing import Any, Iterable, Optional, Union |
34 | 33 |
|
35 | 34 | import substrait.algebra_pb2 as stalg |
36 | | -import substrait.plan_pb2 as stpl |
37 | 35 | import substrait.type_pb2 as stp |
38 | 36 |
|
39 | 37 | from substrait.builders import plan as _plan |
40 | 38 | from substrait.builders import type as _type |
41 | 39 | from substrait.dataframe.expr import Expr, Measure, col, lit |
42 | 40 | from substrait.extension_registry import ExtensionRegistry |
43 | | -from substrait.type_inference import infer_plan_schema, reference_subtrees |
| 41 | +from substrait.type_inference import infer_plan_schema |
44 | 42 |
|
45 | 43 | # All 13 JoinRel.JoinType variants (SET_OP_UNSPECIFIED excluded). "single" |
46 | 44 | # returns at most one right match per left row (runtime error on multiple); |
@@ -126,34 +124,6 @@ def _split_measure(m: Union[Expr, Measure]): |
126 | 124 | return _unbound(m), None |
127 | 125 |
|
128 | 126 |
|
129 | | -class _CteContext: |
130 | | - """Collects shared subtrees while a plan with ``cache()`` is being built.""" |
131 | | - |
132 | | - __slots__ = ("subtrees", "names", "plans", "ordinal_by_token") |
133 | | - |
134 | | - def __init__(self): |
135 | | - self.subtrees: "list[stalg.Rel]" = [] # indexed by subtree_ordinal |
136 | | - self.names: "list[list[str]]" = [] |
137 | | - self.plans: "list[stpl.Plan]" = [] # the resolved subtree plans (extensions) |
138 | | - self.ordinal_by_token: dict = {} |
139 | | - |
140 | | - |
141 | | -# Active while a DataFrame is being materialized; None otherwise. |
142 | | -# |
143 | | -# ``cache()`` needs one accumulator shared by every nested ``resolve()`` in a |
144 | | -# single ``to_plan()`` -- to dedupe repeated uses of a cached frame by identity |
145 | | -# and hand out ``subtree_ordinal``s. The builder contract is |
146 | | -# ``UnboundPlan = Callable[[ExtensionRegistry], Plan]``, which has no slot to |
147 | | -# thread that accumulator, and schema inference (which resolves ReferenceRels |
148 | | -# against the subtree list) runs *inside* the builder layer. Rather than widen |
149 | | -# that contract across every builder, ``cache()`` publishes the accumulator here |
150 | | -# and ``type_inference.reference_subtrees`` exposes its ``.subtrees`` list to |
151 | | -# inference; ``_materialize`` sets both for the duration of the build. |
152 | | -_cte_context: contextvars.ContextVar = contextvars.ContextVar( |
153 | | - "cte_context", default=None |
154 | | -) |
155 | | - |
156 | | - |
157 | 127 | _default_registry: Optional[ExtensionRegistry] = None |
158 | 128 |
|
159 | 129 |
|
@@ -532,15 +502,6 @@ def resolve(registry: ExtensionRegistry): |
532 | 502 | bound = inner(registry) |
533 | 503 | rel = bound.relations[-1].root.input |
534 | 504 | rel_inner = getattr(rel, rel.WhichOneof("rel_type")) |
535 | | - # A few relations (ReferenceRel from .cache(), UpdateRel) carry no |
536 | | - # RelCommon and so cannot hold a hint -- fail with a clear message |
537 | | - # rather than an opaque AttributeError on `.common`. |
538 | | - if "common" not in rel_inner.DESCRIPTOR.fields_by_name: |
539 | | - raise TypeError( |
540 | | - f"cannot attach a hint to a {rel_inner.DESCRIPTOR.name} " |
541 | | - "(e.g. a cached/reference relation); apply .hint(...) before " |
542 | | - ".cache()" |
543 | | - ) |
544 | 505 | common = rel_inner.common |
545 | 506 | if row_count is not None: |
546 | 507 | common.hint.stats.row_count = row_count |
@@ -666,62 +627,13 @@ def write_named_table( |
666 | 627 | ) |
667 | 628 | ) |
668 | 629 |
|
669 | | - def cache(self) -> "DataFrame": |
670 | | - """Mark this DataFrame as a reusable common subplan (a CTE). |
671 | | -
|
672 | | - Every use of the returned frame in the same ``to_plan()`` emits the |
673 | | - subplan once as a shared subtree and references it (``ReferenceRel``), |
674 | | - instead of inlining a fresh copy each time. |
675 | | - """ |
676 | | - inner = self._plan |
677 | | - token = object() # identity for this cached node |
678 | | - |
679 | | - def resolve(registry: ExtensionRegistry) -> stpl.Plan: |
680 | | - ctx = _cte_context.get(None) |
681 | | - if ctx is None: # built without a context -> just inline |
682 | | - return inner(registry) |
683 | | - ordinal = ctx.ordinal_by_token.get(token) |
684 | | - if ordinal is None: |
685 | | - subplan = inner(registry) |
686 | | - ordinal = len(ctx.subtrees) |
687 | | - ctx.ordinal_by_token[token] = ordinal |
688 | | - ctx.subtrees.append(subplan.relations[-1].root.input) |
689 | | - ctx.names.append(list(subplan.relations[-1].root.names)) |
690 | | - ctx.plans.append(subplan) |
691 | | - # Emit a ReferenceRel to the shared subtree, propagating the |
692 | | - # subtree's extensions so builder merges carry them up. |
693 | | - return _plan.reference(ordinal, ctx.names[ordinal], ctx.plans[ordinal])( |
694 | | - registry |
695 | | - ) |
696 | | - |
697 | | - return self._next(resolve) |
698 | | - |
699 | | - def _materialize(self, registry: ExtensionRegistry) -> stpl.Plan: |
700 | | - ctx = _CteContext() |
701 | | - cte_token = _cte_context.set(ctx) |
702 | | - ref_token = reference_subtrees.set(ctx.subtrees) |
703 | | - try: |
704 | | - plan = self._plan(registry) |
705 | | - finally: |
706 | | - _cte_context.reset(cte_token) |
707 | | - reference_subtrees.reset(ref_token) |
708 | | - if not ctx.subtrees: |
709 | | - return plan |
710 | | - # Prepend the shared subtrees; ReferenceRel ordinals index into them. |
711 | | - subtree_rels = [stpl.PlanRel(rel=s) for s in ctx.subtrees] |
712 | | - return stpl.Plan( |
713 | | - version=_plan.default_version, |
714 | | - relations=[*subtree_rels, plan.relations[-1]], |
715 | | - **_plan._merge_extensions(plan, *ctx.plans), |
716 | | - ) |
717 | | - |
718 | 630 | def to_plan(self): |
719 | 631 | """Materialize to a ``substrait.proto.Plan``.""" |
720 | | - return self._materialize(self._registry) |
| 632 | + return self._plan(self._registry) |
721 | 633 |
|
722 | 634 | # Kept for parity with the substrait.narwhals (Narwhals) wrapper's API. |
723 | 635 | def to_substrait(self, registry: Optional[ExtensionRegistry] = None): |
724 | | - return self._materialize(registry or self._registry) |
| 636 | + return self._plan(registry or self._registry) |
725 | 637 |
|
726 | 638 |
|
727 | 639 | class GroupBy: |
|
0 commit comments