Repository navigation
Conversation
|
Important Review skippedReview was skipped as selected files did not have any reviewable changes.
|
| Layer / File(s) | Summary |
|---|---|
Python package and session hooks python/Cargo.toml, python/pyproject.toml, python/einfold/*, python/src/lib.rs, python/README.md, python/Cargo.lock.license, .gitignore |
Adds Python package configuration and native exports for DataFusion session components, planner hooks, codecs, plan counters, and the DataFusion version. The README documents supported query patterns and session integration. |
FFI codecs and planner execution python/src/codec.rs, python/src/bridge.rs |
Adds codecs that transfer table providers and physical plans by reference. The planner bridge relinks matching sum and max functions, tracks plan counts, plans through EinFoldPlanner, and wraps execution in the library runtime. |
Deep-plan handling and validation python/src/stack.rs, python/tests/test_einfold.py, .github/workflows/ci.yml |
Adds Linux stack-limit handling and tests for query results, repeated queries, optional integrations, and deep join plans. CI builds and installs a release wheel before running the Python tests. |
Priority:
Estimated code review effort: 4 (Complex) | ~45 minutes
Change: Feature · Severity of issue fixed: Medium
Sequence Diagram(s)
sequenceDiagram
participant DataFusionSession
participant EinFold
participant Bridge
participant EinFoldPlanner
DataFusionSession->>EinFold: Request session planner
EinFold->>Bridge: Create planner with shared counters
DataFusionSession->>Bridge: Plan logical query
Bridge->>EinFoldPlanner: Plan relinked query with session rules
EinFoldPlanner-->>Bridge: Return physical plan
Bridge-->>DataFusionSession: Return opaque execution plan
Possibly related issues
- xqlsystems/einfold#58 — Adds table and physical-plan codecs, function relinking, and plan handling for the DataFusion Python FFI gaps.
- xqlsystems/einfold#39 — Adds the Python package and session extension needed to use einfold with DataFusion.
- xqlsystems/einfold#111 — Relinks foreign
sumandmaxfunctions to DataFusion’s built-ins. - xqlsystems/einfold#115 — Adds plan counters to the Python planner extension.
- xqlsystems/einfold#95 — Removes Prost’s recursion limit and adds Linux stack-limit handling for deep plans.
- xqlsystems/einfold#84 — Concerns plan handling through the Python extension’s bridge.
- xqlsystems/einfold#77 — Removes Prost’s recursion limit to support deeper logical-plan decoding.
Possibly related PRs
- xqlsystems/einfold#40 — Pins the workspace to DataFusion 55.2.0, which this Python bridge also uses.
- xqlsystems/einfold#110 — Continues the Python package work using the same package, native modules, and bridge.
- xqlsystems/einfold#37 — Adds the fold-planning and execution types that the Python bridge plans and executes.
Merge Risk: 
Moderate · previous review c6c5e
-
Custom aggregates can return incorrect results, and failed queries can retain table data. These risks should be addressed before merging.
Security architecture risk: 
Moderate · up to c6c5e
-
Installing the extension makes query planning part of the host process’s availability boundary. Deep queries can terminate the process on some threads, and interrupted plan transfers lack a complete resource-release guarantee. Security exposure depends on whether the application accepts untrusted SQL and shares a process across users.
Retained concerns
- Medium · security · inferred: The new planner routes session queries through recursive FFI decoding while disabling the protobuf recursion limit. The documented worker-thread and macOS behavior includes process termination for sufficiently deep plans. Raising Linux’s soft stack limit only accommodates the main thread; it does not turn excessive depth into a contained query error. If a host accepts less-trusted SQL, one enabled session can therefore threaten availability of the entire process. The negative subprocess test does not independently attribute the crash to the enabled planner because it runs the baseline first.
- Medium · reliability · inferred: Table and plan references are stored in process-global registries and released only when their identifiers are decoded. There is no per-transfer owner or abort cleanup. Published DataFusion 55.2.0 logical serialization encodes a LIMIT input before rejecting a non-literal limit, providing an encode-before-error path that cannot be cleaned inside Bridge. Such aborted transfers can accumulate identifiers and keep table providers alive after session replacement or disposal, weakening process-level resource containment. Supported Python-query reproduction and exact wheel dependency resolution remain unresolved; successful-query cleanup does not establish failure cleanup.
Security review details
-
Security Blast Radius
- inferred — The availability boundary is the hosting process, not an individual query or session: decoding can terminate the process, and reference registries are shared across sessions. A malicious-SQL attack requires an application to grant query submission to an enabled session; external endpoint, tenant, credential, and deployment exposure were not established.
Security Findings and Attack Paths
- inferred — The supported security concern is failure containment: attacker-selected query depth can reach recursive decoding through the installed planner. A second conditional path is failed serialization after reference registration, which can retain resources. Neither cross-session data theft nor privilege escalation was demonstrated.
Trust Boundaries and Controls
- observed — Capsule names and FFI version checks constrain the supported native interface. Reference decoding rejects malformed and unknown identifiers and consumes valid entries once, but identifiers have no session namespace. No inspected SQL path gives callers control over these encoded identifiers, so their predictability alone does not establish an authorization bypass.
Resilience and Maintainability Implications
- observed — Mutex protection serializes registry mutations and successful decoding releases entries. It does not associate entries with a transfer’s terminal state. Opaque prevents outbound traversal of inner physical nodes, but cannot protect inbound logical serialization that fails before the planner callback.
Hardening Proposals
- proposed — Give each transfer an owner spanning outer serialization through terminal decoding, with error and cancellation cleanup limited to that transfer’s identifiers. Clearing shared registries globally would interfere with concurrent transfers.
- proposed — For hosts accepting untrusted SQL, enforce a plan-complexity bound before recursive decoding or use decoding with bounded or growable stack behavior. Process isolation is an alternative containment boundary; a larger main-thread stack alone is not one.
Pre-merge checks: 
4 · 
1
-
Failed checks: 1 warning Passed checks: 4 passed-
Full details: Description check
-
Explanation
The description is detailed and on topic. It explains the user-facing API, design decisions, packaging, tests, performance results, limitations, and follow-up work. It does not use every template heading or include a completed issue-closing line, but the required information is mostly present.
Full details: Linked Issues check
-
Explanation
[
#77] requires einfold to plan logical plans deeper than prost's default 100-message recursion limit.python/Cargo.tomlenables prost'sno-recursion-limitfeature.python/tests/test_einfold.pycovers join depths 15 and 60 and a 350-join subprocess case. The PR also reports successful planning at greater depth. The issue's fallback suggestion is optional, so its absence is not a gap.
Full details: Out of Scope Changes check
-
Explanation
The changes support the stated DataFusion Python package and [
#77]. The prost configuration and stack handling support deep logical-plan decoding. The bridge, codecs, counters, packaging, CI, README, and integration tests support installing and validatingEinFoldindatafusion-python. No unrelated change is demonstrated by the supplied whole-PR summary.
-
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 27 functions across 7 files.
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
-
Finishing Touches: 💡 1
-
📝 Generate docstrings: 💡
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
🪤 Claude (fuzz adversary) This adds a simulation test of einfold in DataFusion, a soak mode that runs it on fresh seeds for a time budget, and a nightly workflow that runs the soak and files an issue when a property fails. It's modeled on the soaks of ddx, the XQL Systems project for automatic differentiation of SQL queries, and shares their log format and their triage script, so a failure in either project is reported the same way. `detect_vs_host.rs` checks that detection fires on the shapes it should, over a handful of spellings each. `simulation.rs` generates the spellings instead: joins written with `ON` or as comma joins, sides swapped, operands behind subqueries and CTEs, filters and `HAVING`, factors that are expressions, keys of eight types (integers of several widths, unsigned keys past `BIGINT`, text, dictionary text, and floats with `-0.0`, NaNs of both signs and two payloads), NULL slots holding garbage values, empty batches, domains with duplicate keys, the regroup's `CASE` testing columns other than the keys, two folds under `UNION ALL`, a fold of a fold's result, and `ORDER BY … LIMIT` above. Each case runs on plain DataFusion and on DataFusion with einfold, with different partition counts and batch sizes. It also varies the session's settings, the same for both: sort-merge rather than hash joins, no repartitioning, no dynamic filters, and small memory pools (8 KB, 32 KB, 256 KB) that DataFusion's operators can spill from. A sixth shape is a masked fold: a `WHERE` comparing two of a fold's outputs, or a look-alike comparing an output with a summed column, half of them under a domain. The invariants it checks, each tagged in failure reports: - `[error]`: einfold answers wherever DataFusion does. `[masks-error]`: and fails wherever DataFusion fails. - `[schema]`: the same columns, names, types, nullability and metadata, both planned and in the batches. - `[rows]`: the same rows as a multiset, bit for bit with exact values. With ill-conditioned values, within the summation bound `2·(n − 1)·u·Σ|terms|`, where DataFusion computes `n` and `Σ|terms|` by running the same query over twin tables holding 1.0 and `|v|`. - `[host-settings]`: DataFusion gives the same exact answer under the case's settings as under the defaults. Where it doesn't, the bug is DataFusion's, and einfold can't match both. - `[rerun]`: the physical plan, reset with `reset_plan_states` and run again, gives the same rows. DataFusion's own plan, rerun, is the control. Reruns use sessions of their own without dynamic filters, which `reset_plan_states` doesn't support. - `[threads]`: where einfold computed every sum itself, the same bits on another partition count and batch size. - `[partition]`: a mask comparing two of a fold's outputs decides each group whole. So the groups of the mask, of its negation and of its `IS NULL` case are, between them, exactly the unmasked fold's, with the same values. An exact check that needs no oracle. - `[hash]`: every fold detection finds, run through einfold's own kernels (`DetectFolds::without_fallback`), also matches. - `[stall]` (soak only): no case runs longer than two minutes. A watchdog reports a hang as a failure of its seed. Under a small pool, either side may run out of memory where the other doesn't. Those cases are tallied rather than failed. Coverage is asserted on long runs: each shape fires, and the dense algorithm, the hash algorithm and the host's plan each run. Findings, each filed with a minimal reproduction: - #60 (#52): a fold joined back to a domain with duplicate keys gave wrong answers on the host-plan fallback. Fixed on main. - #61 (#48): factors were evaluated below the join, so einfold failed queries DataFusion answers. Fixed on main. - #62 (#48): the fallback wasn't a child of `EinFoldExec`, so a fold in `WITH RECURSIVE` panicked, and a reset plan reran wrongly. Fixed on main. - #66 (#48, from #62's fix): with the fallback now a child, DataFusion's physical optimizer runs over it twice, and some folds fail to plan. - #63 (upstream, already apache/datafusion#26618): DataFusion's TopK gives timing-dependent answers over `-0.0` and `0.0`. - #67 (upstream): DataFusion's sort-merge join on a float key and another key drops a match when a side holds both `-0.0` and `0.0`. - apache/datafusion#24414, fixed after 55.2: a sort-merge join's filter reads stale columns once a projection is pushed into it. - #71 (upstream): DataFusion's plans can hang under a small memory pool when they repartition. - #21, a new spelling: an `=` in `WHERE` over an `IS NOT DISTINCT FROM` join matches NULL to NULL. The shrinker in #104 cut it down to one row per table. - #88 (#78, on M2's stack): a masked fold left-joined from a domain ignores its mask. The upstream ones are pinned in `datafusion_behaviors.rs`, and each pin fails once DataFusion fixes its bug. The simulation steps around each open bug (`Known` in `simulation.rs`), so the soak keeps finding new ones. `EINFOLD_SIM_KNOWN=1` brings them back, to check a fix. It passes 150 cases in a release build (60 in CI's debug build). An ignored `export` test writes generated cases to files, so another host can run them. The Python package's differential runs (#75) used it. #104 adds shrinking on top, through #103's test kit. The nightly workflow runs three ten-minute regions on disjoint seeds, and keeps at most one open `fuzz-finding` issue: a recurrence comments on it rather than opening another. Reproduce any reported failure with `EINFOLD_SEED=<seed> cargo test -p einfold-datafusion --test simulation -- --nocapture simulation`, which prints the case's SQL and tables. <!-- - [x] cargo fmt / clippy / test / doc / reuse lint --> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01SCMAk87qC9VKCk22P7HDH1 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Tests** * Expanded coverage for query behavior involving joins, filters, sorting, signed zeros, and varying partition and batch sizes. * Added randomized comparisons of query results and schemas, repeatability checks, and longer-running simulations. * Nightly and manually started soak runs now test multiple seed regions and summarize failures with reproduction details when available. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
f1c2ce8 to
e0f455a
Compare
3cf61a3 to
2e7e1a2
Compare
…ts, and follow the style guide in the bridge Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…erms mean Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ython does (#83) 🪭 Claude (einfold) For #79 (peak memory as a goal). With einfold installed, a session that ran xarray-sql's GPT loss again and again kept growing. (The GPT is `benchmarks/ml/02_nanogpt.py`, in xqlsystems/xarray-sql#269.) Two causes: - **The codec kept executed plans.** A plan that has run keeps what its operators hold, such as a hash join's table, and the codec kept the last 1,024 plans. That cost about 2.2 GB per query. It's fixed on #75 ("Hold a plan from its encoding to its decoding"), since the code is there. - **The allocator** is this PR. This library allocated with the system allocator, glibc's, which keeps memory per thread, and einfold's kernels run on blocking threads. A session still grew by about 150 MB per query. With `MALLOC_ARENA_MAX=2` the growth stopped, so this is fragmentation, not retention. datafusion-python links mimalloc, and its sessions don't grow, so this library now allocates with mimalloc too. A buffer one library allocates is freed by its own release callback, through Arrow's C data interface, so the two allocators never free each other's memory. The GPT's loss at batch 8, 64 channels, context 32, run ten times in one session, from xarray-sql's environment: | | after the 1st | after the 5th | after the 10th | |---|---:|---:|---:| | plans kept | 2.9 GB | 9.9 GB (killed at 10 GB) | | | plans freed, glibc | 2.8 GB | 3.4 GB | | | plans freed, mimalloc | 3.3 GB | 3.1 GB | 3.1 GB | DataFusion alone: 2.7 GB after the 1st, 2.3 GB after the 5th. The whole demo at that size, three steps, now peaks at 3.6 GB with einfold against 2.7 GB without; before both fixes it reached 5.9 GB in two. With einfold, a training step takes 1.3 s against 2.5 s. The loss still peaks about 600 MB higher with einfold, which #79 is about. **Tests.** The Python suite passes (12 tests). #75's test of running queries again now checks that the codec holds no tables or plans after each query. ## After CodeRabbit's review - **mimalloc 2, not 3:** mimalloc 3 crashes on macOS arm64 when another copy of it runs in the process, as PyArrow 24's does (apache/datafusion-python#1607). #110's release workflow tests the macOS arm64 wheel. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
…ir items The capsule helpers move to capsule.rs, EinFold to ein_fold.rs, the codecs datafusion-python installs to logical_codec.rs, and codec.rs becomes by_reference.rs, after ByReference. Found by CodeRabbit's review of #75. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS
🪭 Claude (einfold)
For the maintainer's request to integrate einfold with xarray-sql's demos.
python/is a Python package,einfold, that installs einfold in any session of datafusion-python (Apache DataFusion's Python bindings), and so in xarray-sql'sContextand ddxdb's:The package is organized by engine:
einfold.datafusionnow, withdatafusionan optional extra (pip install "einfold[datafusion]"), so thateinfold.duckdbcan sit beside it. The README is written for someone who first meets einfold on PyPI.It's spike S29's bridge (
docs/spikes/s29-python-planner), made fit to keep:DetectFolds::detect, so demo 1's attention runs fused.MAXis relinked as well asSUM, in aggregates and windows. Built-ins cross the FFI as foreign wrappers, and detection matches them by type, so the attention pipeline wasn't found untilMAXwas relinked too.no-recursion-limitfeature. xarray-sql's GPT demo (in Add an ml extra for gradients in SQL, and two ML demos xarray-sql#269) now plans and trains with einfold installed, matching DataFusion's loss to2e-16. Deeper plans then overflowed the stack, from about 250 joins, where DataFusion alone plans about 1,350. datafusion-proto decodes a plan, on the thread that asks for it, by recursing without growing its stack (DataFusion 55: datafusion-proto decodes a logical plan without growing its stack, so a deep plan crashes the process — draft for upstream #95). So installing einfold raises the process's soft stack limit to 64 MB, asulimit -swould: Linux grows the main thread's stack on demand up to that limit. A chain of 1,300 joins now plans.From Python (datafusion-python 55.0.0 from PyPI, i7-8700):
50,000 × 16 · 16 × 8SELECT 1The last row is the fixed cost of crossing the FFI, 1.5–3 ms per query. That's for datafusion-python and its FFI to fix (#58).
Packaging. The crate is outside the Cargo workspace, like the benchmarks. It needs
unsafeto read datafusion-python's FFI capsules, which the workspace forbids, and pyo3 and DataFusion's FFI crates. Eachunsafeblock carries aSAFETYcomment, andunsafe_op_in_unsafe_fnis denied. A new CI job builds the wheel with maturin, runs clippy on the crate, and runs the tests against datafusion-python 55.0.0 from PyPI.Tests (
python/tests/test_einfold.py): each query gives the same rows with einfold installed as without, to1e-12, and einfold runs what it should:Contextwithfrom_dataset, and ddxdb'sgrad, both skipped where the packages aren't installed;All 9 pass locally against xarray-sql's environment. CI has neither xarray-sql nor ddxdb, so it runs 7.
Next, in xarray-sql: an
EinFoldhost inbenchmarks/ml/_hosts.py, aDataFusionhost whose context isxql.Context().with_extensions(EinFold()). That's a PR there, once xarray-sql#269 merges.After CodeRabbit's review
sumormaxis replaced by DataFusion's, since nothing crossing the FFI tells them apart (Keep a session's own sum and max in plans einfold plans from Python #111).🤖 Generated with Claude Code
https://claude.ai/code/session_01M96xSez6PZwTmfaGh2eJgS