Skip to content

Share per-chunk JoinLeftData across right partitions in NLJ memory-limited fallback#22038

Open
viirya wants to merge 8 commits into
apache:mainfrom
viirya:nlj-multi-partition-unmatched-fix
Open

Share per-chunk JoinLeftData across right partitions in NLJ memory-limited fallback#22038
viirya wants to merge 8 commits into
apache:mainfrom
viirya:nlj-multi-partition-unmatched-fix

Conversation

@viirya

@viirya viirya commented May 6, 2026

Copy link
Copy Markdown
Member

Which issue does this PR close?

  • Closes #.

Rationale for this change

When target_partitions > 1, the memory-limited fallback path was building a per-output-partition JoinLeftData with AtomicUsize::new(1) for each left chunk, so each partition emitted unmatched left rows based only on its own right-side matches. For LEFT, FULL, LEFT SEMI, LEFT ANTI, and LEFT MARK, this produced wrong results (duplicate unmatched rows; for LEFT SEMI, duplicate matched rows when multiple right partitions matched the same left row).

What changes are included in this PR?

This change introduces a plan-level FallbackCoordinator that:

  • Owns the left spill stream and a single chunk-sized MemoryReservation,
  • Has the first partition reaching a chunk become its "leader": it loads the chunk and publishes an Arc<JoinLeftData> (with probe_threads_counter == right_partition_count) into a shared slot,
  • Lets every other right partition take an Arc clone of the same JoinLeftData, so the visited bitmap and probe-thread counter are shared exactly as in the single-pass collect_left_input path,
  • Releases the slot only after the partition that brings the counter to zero finishes emitting unmatched left rows for the chunk, then notifies waiters so the next chunk can be loaded.

The per-chunk in-flight fetch and release are driven through BoxFuture fields on SpillStateActive, polled across poll_next iterations.

The FULL-join multi-partition guard added in #21833 is removed; FULL joins now use the shared coordination path.

Discussed in #21833 (comment) and #21833 (comment).

Are these changes tested?

Added five multi-partition correctness tests. test_overallocation is updated to expect FULL multi-partition to spill (not OOM). Added multi-partition NLJ spill SLT cases.

Are there any user-facing changes?

No

@github-actions github-actions Bot added sqllogictest SQL Logic Tests (.slt) physical-plan Changes to the physical-plan crate labels May 6, 2026
@viirya
viirya force-pushed the nlj-multi-partition-unmatched-fix branch from a1a8bbe to 15e05fe Compare June 20, 2026 20:41
@viirya
viirya requested a review from kosiew June 20, 2026 23:11
kosiew
kosiew previously requested changes Jun 22, 2026

@kosiew kosiew 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.

@viirya
Thanks for working on this. I think the coordinator changes need one more regression test before this lands, specifically one that exercises the multi-chunk path.

04)------AggregateExec: mode=Partial, gby=[], aggr=[count(Int64(1))], metrics=[<slt:ignore>]
05)--------NestedLoopJoinExec: join_type=Left, filter=v1@0 + v2@1 = 101, projection=[], metrics=[output_rows=5.00 K, <slt:ignore> spill_count=2, <slt:ignore>]
06)----------ProjectionExec: expr=[value@0 as v1], metrics=[<slt:ignore>]
07)------------LazyMemoryExec: partitions=1, batch_generators=[generate_series: start=1, end=5000, batch_size=8192], metrics=[<slt:ignore>]

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.

Thanks for adding the regression coverage here. I do not think this currently exercises the multi-chunk coordinator path though.

The plan shows the left generate_series(1, 5000) being produced as one batch_size=8192 batch, and load_one_chunk accepts a single over-budget batch so it can make progress. Because of that, these SLT cases appear to validate only the one-chunk fallback path.

The main invariant added by the coordinator is per-chunk sharing, followed by releasing the current chunk and advancing to the next one. Without a test where the left input is split into at least two chunks, the carryover, release_chunk, waiter notification, and global-right accumulation across chunks are not really covered.

Could you please add a multi-partition spill regression with multiple left batches or chunks? For example, this could be a Rust test that feeds more than one left batch, or an SLT shape that produces multiple left batches. It would be good to assert the same LEFT/FULL or LEFT SEMI/ANTI counts, plus spill_count > 0.

);
}

// Case 1: requested chunk is already loaded.

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.

Small follow-up suggestion: while a leader is loading a chunk, waiters can see left_stream.is_none() and reopen the spill stream before they reach the loader_in_flight wait path.

The leader later overwrites that stream, so this looks like wasted I/O and state churn. It may be worth guarding lazy stream/reservation initialization with !inner.loader_in_flight, or moving that initialization into the leader-claim branch so only the loader sets up those fields.

viirya added a commit to viirya/arrow-datafusion that referenced this pull request Jun 22, 2026
Previously the shared left spill stream and chunk reservation were
lazily initialized at the top of `FallbackCoordinator::next_chunk`,
before the `loader_in_flight` guard. While a leader was loading a chunk,
a waiting partition could observe `left_stream.is_none()` and reopen the
spill stream, which the leader would then overwrite — wasted I/O and
state churn.

Move the lazy initialization into the leader-claim branch (under the
`loader_in_flight` guard), so only the leader sets up the stream,
schema, and reservation. Waiters fall straight through to the notify
wait path.

Addresses a review suggestion on apache#22038.

Co-authored-by: Claude Code
viirya added a commit to viirya/arrow-datafusion that referenced this pull request Jun 22, 2026
The existing multi-partition spill tests feed the left side as a single
batch, so the memory-limited fallback only ever loads one chunk. That
leaves the coordinator's multi-chunk machinery untested: `carryover`
between chunks, `release_chunk` advancing to the next chunk, waiter
notification, and (for FULL) the global right-unmatched bitmap
accumulated across chunks.

Add `*_multi_chunk_left_join` and `*_multi_chunk_full_join`: the left
input is one row per batch under a tight memory limit, so the
coordinator loads each row as a separate chunk (verified to load
multiple chunks with spill_count > 0). Both assert exact row sets, so a
regression in per-chunk `JoinLeftData` sharing (duplicate unmatched
rows) or cross-chunk right accumulation would fail.

These collect the four output partitions concurrently: a chunk is not
released until all partitions finish probing it, so sequential
collection would deadlock — concurrent collection mirrors how partitions
run under the runtime. Also drop a stray `dbg!` in `join_inner_with_filter`.

Addresses review feedback on apache#22038.

Co-authored-by: Claude Code
@viirya

viirya commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

@kosiew Thanks for the careful review — both points were spot on.

Multi-chunk coverage. You're right that the existing SLT cases only exercised the single-chunk path: generate_series(1, 5000) arrives as one 8192-row batch, so load_one_chunk accepts the single over-budget batch and the coordinator never loads a second chunk. The spill_count there comes from the right side, not from left chunking. I couldn't reliably target multi-chunk from SLT (a memory limit tight enough to split the left side OOMs other operators first), so I added Rust regression tests instead: test_nlj_memory_limited_multi_partition_multi_chunk_{left,full}_join. The left input is one row per batch under a tight memory limit, which I verified loads multiple distinct chunks with spill_count > 0. They assert exact row sets, so a regression in per-chunk JoinLeftData sharing (duplicate unmatched rows) or in the cross-chunk global right-unmatched accumulation (FULL) would fail.

One thing worth noting: these tests collect the four output partitions concurrently. Sequential collection deadlocks on the multi-chunk path — a chunk isn't released until every partition finishes probing it (the probe_threads_counter reaching zero), and no partition can advance to the next chunk until the current one is released. So if only one partition is driven at a time, it blocks waiting for a release that can never happen. Concurrent collection mirrors how partitions actually run under the runtime; I documented this in the helper.

Leader-only stream init. Good catch — moved the lazy stream/schema/reservation initialization into the leader-claim branch (under the loader_in_flight guard) so waiters no longer open a throwaway spill stream that the leader overwrites.

Pushed both as separate commits. Ready for another look when you have a chance.

@kosiew kosiew 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.

@viirya

Thanks for the updates. I re-reviewed the changes and the two follow-ups I raised have been addressed:

  • Added multi-chunk regression coverage with concurrent output partitions, strict memory pressure, exact LEFT/FULL output assertions, and spill verification.
  • Moved stream initialization under the leader claim so waiting tasks no longer create throwaway spill streams.

I only have one small non-blocking suggestion.

None => {
let stream = spill_data
.spill_manager
.read_spill_as_stream(spill_data.spill_file.clone(), None)?;

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.

Nice improvement moving stream initialization behind the leader claim.

One small thing that caught my eye: loader_in_flight is set to true before calling read_spill_as_stream(...), and the call uses ?. Today that seems safe because read_spill_as_stream only constructs the buffered stream and returns Ok(...); any real file/open/schema errors happen later while polling the stream, where load_one_chunk already clears loader_in_flight and wakes any waiters.

This feels a little fragile though. If stream construction ever becomes genuinely fallible in the future, an early return here could leave waiters blocked indefinitely. It might be worth avoiding ? while the flag is set, or using a small guard/helper that guarantees the flag is cleared and waiters are notified if leader setup fails.

@viirya viirya Jun 24, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — agreed it's worth hardening even though it isn't reachable today (read_spill_as_stream only builds the buffered stream; the real I/O errors surface later while polling, where load_one_chunk already clears loader_in_flight and notifies).

Fixed: on stream-construction failure the leader now clears loader_in_flight, drops the lock, wakes waiters, and propagates the error, so another partition can claim the leader role and retry rather than blocking on a release that never comes. Pushed as a separate commit.

@kosiew
kosiew dismissed their stale review June 24, 2026 04:18

addressed

@kosiew

kosiew commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Leaving this open for more review as this is a big PR.

viirya added a commit to viirya/arrow-datafusion that referenced this pull request Jun 24, 2026
…ails

In `FallbackCoordinator::next_chunk`, the leader sets `loader_in_flight`
before constructing the spill stream. If that construction returned an
error via `?`, the leader would bail out while still holding
`loader_in_flight = true` and without notifying waiters — leaving every
other partition blocked forever on a chunk release that the failed
leader can never make.

`read_spill_as_stream` only builds the buffered stream today (real I/O
errors surface later while polling, where the flag is already cleared),
so this is not currently reachable. Handle it defensively anyway: on
construction failure, clear `loader_in_flight`, drop the lock, wake
waiters, and propagate the error so another partition can retry.

Addresses a review suggestion on apache#22038.

Co-authored-by: Claude Code
@andygrove

andygrove commented Jul 2, 2026

Copy link
Copy Markdown
Member

Thanks for fixing this. A question about distributed execution.

Downstream engines like Ballista and datafusion-distributed run each output partition of a plan as an independent task, often in a separate process. Each task deserializes and instantiates the physical plan on its own and calls execute(partition) for only its one partition.

If I read FallbackCoordinator correctly, it initializes probe_threads_counter to right_partition_count and loads the next left chunk only after the partition that brings the counter to zero notifies the waiters. In a distributed setup a process builds its own FallbackCoordinator but polls a single right partition, so the counter would never reach zero and the Notify waiters would wait forever. Would that stall the memory-limited fallback whenever the right side is spread across tasks?

Could a distributed consumer opt out of this cross-partition coordination through a config flag, the way datafusion.optimizer.enable_dynamic_filter_pushdown lets them disable another single-process assumption? #22671 looks like it already takes the distributed-safe route for left-emitting joins by disabling the fallback instead of coordinating across partitions.

This review was assisted by an LLM.

viirya added a commit to viirya/arrow-datafusion that referenced this pull request Jul 18, 2026
The memory-limited NestedLoopJoin fallback coordinates per-chunk left
state (visited bitmap + probe-thread counter) across all right-side
partitions via `FallbackCoordinator`. That coordination assumes every
right partition runs in the same process. Distributed engines (Ballista,
datafusion-distributed) execute each output partition as an independent
task that builds its own coordinator and polls a single partition, so
the shared probe-thread counter never reaches zero, no partition is
elected to release the current chunk, and the waiters block forever —
the memory-limited fallback deadlocks.

Add `datafusion.execution.enable_nlj_coordinated_fallback` (default
true, preserving current behavior). When set to false, the coordinated
fallback is disabled for the join types that would deadlock —
left-emitting joins (LEFT, LEFT SEMI, LEFT ANTI, LEFT MARK, FULL) with a
multi-partition right side — which then use `SpillState::Disabled` and
fail with resource exhaustion under memory pressure rather than
deadlocking. Single-partition and non-left-emitting joins are always
safe and keep the coordinated fallback regardless of the flag.

Raised by @andygrove on apache#22038.

Co-authored-by: Claude Code
@github-actions github-actions Bot added documentation Improvements or additions to documentation common Related to common crate labels Jul 18, 2026
@viirya

viirya commented Jul 18, 2026

Copy link
Copy Markdown
Member Author

@andygrove Thanks for looking at this closely — your analysis is correct, and it's a real deadlock, not a stall in theory.

To confirm the mechanism: the coordinator initializes probe_threads_counter to the global right_partition_count, and a chunk is only released (and the next one loaded) once the partition that drives the counter to zero notifies the waiters. When a process builds its own FallbackCoordinator but only executes one right partition, the counter stops at N-1, nobody is elected to release the chunk, and the other next_chunk waiters block forever on the Notify.

One nuance I want to be honest about: the single-process assumption itself isn't new — the single-pass path (collect_left_input(..., right_partition_count)) already sets the counter to the global partition count, and a distributed run there produces incorrect results (missing unmatched-left rows) because non-emitter partitions just return early. What this PR changes is that the coordinator makes that assumption load-bearing for liveness, turning a wrong-results degradation into a hang — a strictly worse failure mode. So the concern is fair and specific to the coordinated fallback.

I've pushed an opt-out along the lines you suggested: a new config flag datafusion.execution.enable_nlj_coordinated_fallback (default true). When a distributed consumer sets it to false, the coordinated fallback is disabled for exactly the cases that would deadlock — left-emitting joins with a multi-partition right side — and those fall back to SpillState::Disabled (resource exhaustion under memory pressure) instead of coordinating, mirroring how enable_dynamic_filter_pushdown lets consumers opt out of another single-process assumption. Single-partition and non-left-emitting joins keep the coordinated fallback regardless.

I kept the default as true so single-process users (the common case) get the coordinated spill behavior without configuration, and distributed engines flip it off — but I don't have a strong opinion on the default and am happy to invert it if you think distributed-safety should be the default, given a hang is a harder failure than an OOM.

viirya added 8 commits July 18, 2026 18:59
Adds five tests reproducing the cross-partition coordination bug in
NestedLoopJoinExec's memory-limited fallback path: each output
partition independently constructs a per-chunk JoinLeftData with
AtomicUsize::new(1), so the left visited bitmap and probe-thread
counter are not shared across right partitions. For LEFT, FULL,
LEFT SEMI, LEFT ANTI, and LEFT MARK, this causes wrong results when
target_partitions > 1 (duplicate unmatched rows; for LEFT SEMI,
duplicate matched rows when multiple right partitions match the same
left row).

The tests are #[ignore]'d for now (with reason) and re-enabled in a
follow-up commit that implements the shared-state fix. Discussed in
apache#21833 (comment)
and apache#21833 (comment)

Co-authored-by: Claude Code
…ons in NLJ memory-limited fallback

When `target_partitions > 1`, the memory-limited fallback path was building
a per-output-partition `JoinLeftData` with `AtomicUsize::new(1)` for each
left chunk, so each partition emitted unmatched left rows based only on
its own right-side matches. For LEFT, FULL, LEFT SEMI, LEFT ANTI, and
LEFT MARK, this produced wrong results (duplicate unmatched rows; for
LEFT SEMI, duplicate matched rows when multiple right partitions matched
the same left row).

This change introduces a plan-level `FallbackCoordinator` that:
- Owns the left spill stream and a single chunk-sized `MemoryReservation`,
- Has the first partition reaching a chunk become its "leader": it loads
  the chunk and publishes an `Arc<JoinLeftData>` (with
  `probe_threads_counter == right_partition_count`) into a shared slot,
- Lets every other right partition take an `Arc` clone of the same
  `JoinLeftData`, so the visited bitmap and probe-thread counter are
  shared exactly as in the single-pass `collect_left_input` path,
- Releases the slot only after the partition that brings the counter
  to zero finishes emitting unmatched left rows for the chunk, then
  notifies waiters so the next chunk can be loaded.

The per-chunk in-flight fetch and release are driven through `BoxFuture`
fields on `SpillStateActive`, polled across `poll_next` iterations.

The FULL-join multi-partition guard added in apache#21833 is removed; FULL
joins now use the shared coordination path. The five
`#[ignore]`-d multi-partition correctness tests added in the previous
commit are unignored and now pass. `test_overallocation` is updated to
expect FULL multi-partition to spill (not OOM).

Discussed in
apache#21833 (comment)
and
apache#21833 (comment)

Co-authored-by: Claude Code
Adds end-to-end SLT cases under target_partitions=4 + tight
memory_limit, covering LEFT, FULL, LEFT SEMI, and LEFT ANTI joins.
Each query uses a non-equi predicate that forces NLJ and verifies
that left-row counts (matched/unmatched) match the single-partition
expectation, exercising the cross-partition shared-state path
introduced in the previous commit.

Co-authored-by: Claude Code
The previous SLT cases verified output correctness under
target_partitions=4 + tight memory_limit, but did not assert that
the memory-limited fallback path was actually taken. Add an
EXPLAIN ANALYZE assertion that the NestedLoopJoinExec line shows
spill_count=2, confirming both the left-side and right-side spills
fired.

Co-authored-by: Claude Code
Previously the shared left spill stream and chunk reservation were
lazily initialized at the top of `FallbackCoordinator::next_chunk`,
before the `loader_in_flight` guard. While a leader was loading a chunk,
a waiting partition could observe `left_stream.is_none()` and reopen the
spill stream, which the leader would then overwrite — wasted I/O and
state churn.

Move the lazy initialization into the leader-claim branch (under the
`loader_in_flight` guard), so only the leader sets up the stream,
schema, and reservation. Waiters fall straight through to the notify
wait path.

Addresses a review suggestion on apache#22038.

Co-authored-by: Claude Code
The existing multi-partition spill tests feed the left side as a single
batch, so the memory-limited fallback only ever loads one chunk. That
leaves the coordinator's multi-chunk machinery untested: `carryover`
between chunks, `release_chunk` advancing to the next chunk, waiter
notification, and (for FULL) the global right-unmatched bitmap
accumulated across chunks.

Add `*_multi_chunk_left_join` and `*_multi_chunk_full_join`: the left
input is one row per batch under a tight memory limit, so the
coordinator loads each row as a separate chunk (verified to load
multiple chunks with spill_count > 0). Both assert exact row sets, so a
regression in per-chunk `JoinLeftData` sharing (duplicate unmatched
rows) or cross-chunk right accumulation would fail.

These collect the four output partitions concurrently: a chunk is not
released until all partitions finish probing it, so sequential
collection would deadlock — concurrent collection mirrors how partitions
run under the runtime. Also drop a stray `dbg!` in `join_inner_with_filter`.

Addresses review feedback on apache#22038.

Co-authored-by: Claude Code
…ails

In `FallbackCoordinator::next_chunk`, the leader sets `loader_in_flight`
before constructing the spill stream. If that construction returned an
error via `?`, the leader would bail out while still holding
`loader_in_flight = true` and without notifying waiters — leaving every
other partition blocked forever on a chunk release that the failed
leader can never make.

`read_spill_as_stream` only builds the buffered stream today (real I/O
errors surface later while polling, where the flag is already cleared),
so this is not currently reachable. Handle it defensively anyway: on
construction failure, clear `loader_in_flight`, drop the lock, wake
waiters, and propagate the error so another partition can retry.

Addresses a review suggestion on apache#22038.

Co-authored-by: Claude Code
The memory-limited NestedLoopJoin fallback coordinates per-chunk left
state (visited bitmap + probe-thread counter) across all right-side
partitions via `FallbackCoordinator`. That coordination assumes every
right partition runs in the same process. Distributed engines (Ballista,
datafusion-distributed) execute each output partition as an independent
task that builds its own coordinator and polls a single partition, so
the shared probe-thread counter never reaches zero, no partition is
elected to release the current chunk, and the waiters block forever —
the memory-limited fallback deadlocks.

Add `datafusion.execution.enable_nlj_coordinated_fallback` (default
true, preserving current behavior). When set to false, the coordinated
fallback is disabled for the join types that would deadlock —
left-emitting joins (LEFT, LEFT SEMI, LEFT ANTI, LEFT MARK, FULL) with a
multi-partition right side — which then use `SpillState::Disabled` and
fail with resource exhaustion under memory pressure rather than
deadlocking. Single-partition and non-left-emitting joins are always
safe and keep the coordinated fallback regardless of the flag.

Raised by @andygrove on apache#22038.

Co-authored-by: Claude Code
@viirya
viirya force-pushed the nlj-multi-partition-unmatched-fix branch from fdb63b4 to 8f2fd93 Compare July 19, 2026 02:19
@github-actions

Copy link
Copy Markdown

Thank you for opening this pull request!

Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch).

Details
     Cloning apache/main
    Building datafusion-common v54.0.0 (current)
       Built [  33.300s] (current)
     Parsing datafusion-common v54.0.0 (current)
      Parsed [   0.057s] (current)
    Building datafusion-common v54.0.0 (baseline)
       Built [  32.611s] (baseline)
     Parsing datafusion-common v54.0.0 (baseline)
      Parsed [   0.058s] (baseline)
    Checking datafusion-common v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.939s] 223 checks: 222 pass, 1 fail, 0 warn, 30 skip

--- failure constructible_struct_adds_field: externally-constructible struct adds field ---

Description:
A pub struct constructible with a struct literal has a new pub field. Existing struct literals must be updated to include the new field.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/constructible_struct_adds_field.ron

Failed in:
  field ExecutionOptions.enable_nlj_coordinated_fallback in /home/runner/work/datafusion/datafusion/datafusion/common/src/config.rs:723

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  68.728s] datafusion-common
    Building datafusion-physical-plan v54.0.0 (current)
       Built [  36.151s] (current)
     Parsing datafusion-physical-plan v54.0.0 (current)
      Parsed [   0.140s] (current)
    Building datafusion-physical-plan v54.0.0 (baseline)
       Built [  36.410s] (baseline)
     Parsing datafusion-physical-plan v54.0.0 (baseline)
      Parsed [   0.138s] (baseline)
    Checking datafusion-physical-plan v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.889s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  75.421s] datafusion-physical-plan
    Building datafusion-sqllogictest v54.0.0 (current)
       Built [ 168.033s] (current)
     Parsing datafusion-sqllogictest v54.0.0 (current)
      Parsed [   0.022s] (current)
    Building datafusion-sqllogictest v54.0.0 (baseline)
       Built [ 163.507s] (baseline)
     Parsing datafusion-sqllogictest v54.0.0 (baseline)
      Parsed [   0.023s] (baseline)
    Checking datafusion-sqllogictest v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.111s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [ 334.659s] datafusion-sqllogictest

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto detected api change Auto detected API change common Related to common crate documentation Improvements or additions to documentation physical-plan Changes to the physical-plan crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants