This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new 66c3840c02 minor(test): strengthen sort-merge join spilling coverage 
(#23988)
66c3840c02 is described below

commit 66c3840c029e839eb8d49d5dd721a4d1735152aa
Author: Burak Şen <[email protected]>
AuthorDate: Sun Aug 2 13:11:40 2026 +0300

    minor(test): strengthen sort-merge join spilling coverage (#23988)
    
    ## Which issue does this PR close?
     - Part of #13431
    
     ## Rationale for this change
    Contuining the work after #23947. Now `SortMergeJoinExec` supports
    spilling under memory pressure, but existing tests do not consistently
    assert that spilling occurs.
    
     ## What changes are included in this PR?
    Adds spill fuzz coverage, constrained-memory stress tests for
    materializing joins, and process-isolated memory-limit validation.
    
     ## Are these changes tested?
     test only change and it can be tested via:
    
    ```
    
    cargo test -p datafusion --test fuzz --features extended_tests spill
    cargo test -p datafusion --test core_integration --features extended_tests 
smj_
    
    ```
    
     ## Are there any user-facing changes?
     no test only change
---
 .../memory_limit/memory_limit_validation/mod.rs    |   1 +
 .../memory_limit_validation/smj_mem_validation.rs  | 105 +++++++++
 .../memory_limit_validation/sort_mem_validation.rs |  48 +---
 .../memory_limit/memory_limit_validation/utils.rs  | 107 ++++++++-
 .../test_files/sort_merge_join_spill.slt           | 252 +++++++++++++++++++++
 5 files changed, 469 insertions(+), 44 deletions(-)

diff --git a/datafusion/core/tests/memory_limit/memory_limit_validation/mod.rs 
b/datafusion/core/tests/memory_limit/memory_limit_validation/mod.rs
index 32df6c5d62..83ebb266c8 100644
--- a/datafusion/core/tests/memory_limit/memory_limit_validation/mod.rs
+++ b/datafusion/core/tests/memory_limit/memory_limit_validation/mod.rs
@@ -18,5 +18,6 @@
 //! Validates query's actual memory usage is consistent with the specified 
memory
 //! limit.
 
+mod smj_mem_validation;
 mod sort_mem_validation;
 mod utils;
diff --git 
a/datafusion/core/tests/memory_limit/memory_limit_validation/smj_mem_validation.rs
 
b/datafusion/core/tests/memory_limit/memory_limit_validation/smj_mem_validation.rs
new file mode 100644
index 0000000000..3af642fffe
--- /dev/null
+++ 
b/datafusion/core/tests/memory_limit/memory_limit_validation/smj_mem_validation.rs
@@ -0,0 +1,105 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Memory-limit validation tests for sort-merge join queries.
+//!
+//! These tests run in separate processes to accurately measure memory usage.
+
+use datafusion::prelude::SessionConfig;
+
+use crate::memory_limit::memory_limit_validation::utils;
+
+/// Ensures the planner selected a sort-merge join.
+const SMJ_OPERATOR_NAME: &str = "SortMergeJoinExec";
+
+/// Configure a two-partition sort-merge join and reduce the sort reservation 
so
+/// the join can spill under the tested memory limits.
+fn smj_session_config() -> SessionConfig {
+    SessionConfig::new()
+        .with_target_partitions(2)
+        .with_sort_spill_reservation_bytes(1024 * 1024)
+        .set_bool("datafusion.optimizer.prefer_hash_join", false)
+}
+
+/// Build a join with one large buffered key group and scalar output.
+fn smj_sum_query(series_len: usize) -> String {
+    format!(
+        "SELECT sum(rr.v) FROM generate_series(0, 0) AS l(k) \
+         JOIN (SELECT i % 1 AS k, i AS v FROM generate_series(1, {series_len}) 
AS r(i)) rr \
+         ON l.k = rr.k"
+    )
+}
+
+#[test]
+fn smj_with_mem_limit_1_runner() {
+    utils::spawn_test_process("smj_mem_validation", "smj_with_mem_limit_1");
+}
+
+#[test]
+fn smj_with_mem_limit_2_runner() {
+    utils::spawn_test_process("smj_mem_validation", "smj_with_mem_limit_2");
+}
+
+#[test]
+fn smj_no_mem_limit_runner() {
+    utils::spawn_test_process("smj_mem_validation", "smj_no_mem_limit");
+}
+
+/// Verify a 40 MB pool forces spilling within the RSS allowance.
+#[tokio::test]
+async fn smj_with_mem_limit_1() {
+    utils::validate_query_with_memory_limits_and_config(
+        40_000_000 * 4,
+        Some(40_000_000),
+        &smj_sum_query(5_000_000),
+        &smj_sum_query(500_000),
+        smj_session_config(),
+        Some(SMJ_OPERATOR_NAME),
+        Some(true),
+    )
+    .await;
+}
+
+/// Verify a 16 MB pool forces spilling. The 5M join keys (~40 MB) stay 
resident
+/// independently of the pool limit, so this case needs a larger RSS allowance.
+#[tokio::test]
+async fn smj_with_mem_limit_2() {
+    utils::validate_query_with_memory_limits_and_config(
+        16_000_000 * 12,
+        Some(16_000_000),
+        &smj_sum_query(5_000_000),
+        &smj_sum_query(500_000),
+        smj_session_config(),
+        Some(SMJ_OPERATOR_NAME),
+        Some(true),
+    )
+    .await;
+}
+
+#[tokio::test]
+async fn smj_no_mem_limit() {
+    utils::validate_query_with_memory_limits_and_config(
+        40_000_000 * 5,
+        None,
+        &smj_sum_query(5_000_000),
+        &smj_sum_query(500_000),
+        smj_session_config(),
+        Some(SMJ_OPERATOR_NAME),
+        Some(false),
+    )
+    .await;
+}
diff --git 
a/datafusion/core/tests/memory_limit/memory_limit_validation/sort_mem_validation.rs
 
b/datafusion/core/tests/memory_limit/memory_limit_validation/sort_mem_validation.rs
index bf04123fff..b55a3039ec 100644
--- 
a/datafusion/core/tests/memory_limit/memory_limit_validation/sort_mem_validation.rs
+++ 
b/datafusion/core/tests/memory_limit/memory_limit_validation/sort_mem_validation.rs
@@ -21,7 +21,6 @@
 //! This file is organized as:
 //! - Test runners that spawn individual test processes
 //! - Test cases that contain the actual validation logic
-use std::{process::Command, str};
 
 use crate::memory_limit::memory_limit_validation::utils;
 
@@ -32,67 +31,40 @@ use crate::memory_limit::memory_limit_validation::utils;
 
 #[test]
 fn memory_limit_validation_runner_works_runner() {
-    spawn_test_process("memory_limit_validation_runner_works");
+    utils::spawn_test_process(
+        "sort_mem_validation",
+        "memory_limit_validation_runner_works",
+    );
 }
 
 #[test]
 fn sort_no_mem_limit_runner() {
-    spawn_test_process("sort_no_mem_limit");
+    utils::spawn_test_process("sort_mem_validation", "sort_no_mem_limit");
 }
 
 #[test]
 fn sort_with_mem_limit_1_runner() {
-    spawn_test_process("sort_with_mem_limit_1");
+    utils::spawn_test_process("sort_mem_validation", "sort_with_mem_limit_1");
 }
 
 #[test]
 fn sort_with_mem_limit_2_runner() {
-    spawn_test_process("sort_with_mem_limit_2");
+    utils::spawn_test_process("sort_mem_validation", "sort_with_mem_limit_2");
 }
 
 #[test]
 fn sort_with_mem_limit_3_runner() {
-    spawn_test_process("sort_with_mem_limit_3");
+    utils::spawn_test_process("sort_mem_validation", "sort_with_mem_limit_3");
 }
 
 #[test]
 fn sort_with_mem_limit_2_cols_1_runner() {
-    spawn_test_process("sort_with_mem_limit_2_cols_1");
+    utils::spawn_test_process("sort_mem_validation", 
"sort_with_mem_limit_2_cols_1");
 }
 
 #[test]
 fn sort_with_mem_limit_2_cols_2_runner() {
-    spawn_test_process("sort_with_mem_limit_2_cols_2");
-}
-
-/// Helper function that executes a test in a separate process with the 
required
-/// environment variable set. Re-invokes the current test binary directly,
-/// avoiding cargo overhead and recompilation.
-fn spawn_test_process(test: &str) {
-    let test_path =
-        
format!("memory_limit::memory_limit_validation::sort_mem_validation::{test}");
-
-    let exe = std::env::current_exe().expect("Failed to get test binary path");
-
-    let output = Command::new(exe)
-        .arg(&test_path)
-        .arg("--exact")
-        .arg("--nocapture")
-        .env("DATAFUSION_TEST_MEM_LIMIT_VALIDATION", "1")
-        .output()
-        .expect("Failed to execute test command");
-
-    let stdout = str::from_utf8(&output.stdout).unwrap_or("");
-    let stderr = str::from_utf8(&output.stderr).unwrap_or("");
-
-    assert!(
-        output.status.success(),
-        "Test '{}' failed with status: {}\nstdout:\n{}\nstderr:\n{}",
-        test,
-        output.status,
-        stdout,
-        stderr
-    );
+    utils::spawn_test_process("sort_mem_validation", 
"sort_with_mem_limit_2_cols_2");
 }
 
 // ===========================================================================
diff --git 
a/datafusion/core/tests/memory_limit/memory_limit_validation/utils.rs 
b/datafusion/core/tests/memory_limit/memory_limit_validation/utils.rs
index 2c9fae20c8..788b8f4942 100644
--- a/datafusion/core/tests/memory_limit/memory_limit_validation/utils.rs
+++ b/datafusion/core/tests/memory_limit/memory_limit_validation/utils.rs
@@ -16,11 +16,14 @@
 // under the License.
 
 use datafusion_common_runtime::SpawnedTask;
+use std::process::Command;
+use std::str;
 use std::sync::Arc;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System};
 use tokio::time::{Duration, interval};
 
+use datafusion::physical_plan::{ExecutionPlan, collect, displayable};
 use datafusion::prelude::{SessionConfig, SessionContext};
 use datafusion_common::human_readable_size;
 use datafusion_execution::{memory_pool::FairSpillPool, 
runtime_env::RuntimeEnvBuilder};
@@ -98,6 +101,42 @@ where
     (result, peak_rss)
 }
 
+/// Helper function that executes a test in a separate process with the 
required
+/// environment variable set. Re-invokes the current test binary directly,
+/// avoiding cargo overhead and recompilation.
+pub fn spawn_test_process(module: &str, test: &str) {
+    let test_path = 
format!("memory_limit::memory_limit_validation::{module}::{test}");
+    let exe = std::env::current_exe().expect("Failed to get test binary path");
+    let output = Command::new(exe)
+        .arg(&test_path)
+        .arg("--exact")
+        .arg("--nocapture")
+        .env("DATAFUSION_TEST_MEM_LIMIT_VALIDATION", "1")
+        .output()
+        .expect("Failed to execute test command");
+
+    let stdout = str::from_utf8(&output.stdout).unwrap_or("");
+    let stderr = str::from_utf8(&output.stderr).unwrap_or("");
+    assert!(
+        output.status.success(),
+        "Test '{test}' failed with status: 
{}\nstdout:\n{stdout}\nstderr:\n{stderr}",
+        output.status,
+    );
+}
+
+fn operator_spill_count(plan: &dyn ExecutionPlan, operator_name: &str) -> 
usize {
+    let own = if plan.name() == operator_name {
+        plan.metrics().and_then(|m| m.spill_count()).unwrap_or(0)
+    } else {
+        0
+    };
+    own + plan
+        .children()
+        .into_iter()
+        .map(|child| operator_spill_count(child.as_ref(), operator_name))
+        .sum::<usize>()
+}
+
 /// Query runner that validates the memory usage of the query.
 ///
 /// Note this function is supposed to run in a separate process for accurate 
memory
@@ -132,6 +171,30 @@ pub async fn validate_query_with_memory_limits(
     mem_limit_bytes: Option<i64>,
     query: &str,
     baseline_query: &str,
+) {
+    let session_config = SessionConfig::new().with_target_partitions(4); // 
Make sure the configuration is the same if test is running on different machines
+    validate_query_with_memory_limits_and_config(
+        expected_mem_bytes,
+        mem_limit_bytes,
+        query,
+        baseline_query,
+        session_config,
+        None,
+        None,
+    )
+    .await;
+}
+
+/// Validate memory usage with a custom session configuration and optional
+/// operator and spill assertions.
+pub async fn validate_query_with_memory_limits_and_config(
+    expected_mem_bytes: i64,
+    mem_limit_bytes: Option<i64>,
+    query: &str,
+    baseline_query: &str,
+    session_config: SessionConfig,
+    expected_operator_name: Option<&str>,
+    expected_operator_spill: Option<bool>,
 ) {
     if std::env::var("DATAFUSION_TEST_MEM_LIMIT_VALIDATION").is_err() {
         println!("Skipping test because DATAFUSION_TEST_MEM_LIMIT_VALIDATION 
is not set");
@@ -151,18 +214,50 @@ pub async fn validate_query_with_memory_limits(
         None => runtime_builder.build_arc().unwrap(),
     };
 
-    let session_config = SessionConfig::new().with_target_partitions(4); // 
Make sure the configuration is the same if test is running on different machines
-
     let ctx = SessionContext::new_with_config_rt(session_config, runtime);
 
     let df = ctx.sql(query).await.unwrap();
+    let physical_plan = df.create_physical_plan().await.unwrap();
+
+    if let Some(expected) = expected_operator_name {
+        let plan_display = 
displayable(physical_plan.as_ref()).indent(true).to_string();
+        assert!(
+            plan_display.contains(expected),
+            "expected physical plan to contain `{expected}`, but 
got:\n{plan_display}",
+        );
+    }
+
     // Run a query with 10% data to estimate the constant overhead
-    let df_small = ctx.sql(baseline_query).await.unwrap();
+    let baseline_plan = ctx
+        .sql(baseline_query)
+        .await
+        .unwrap()
+        .create_physical_plan()
+        .await
+        .unwrap();
+    let baseline_task_ctx = ctx.task_ctx();
+    let (_, baseline_max_rss) = measure_max_rss(|| async move {
+        collect(baseline_plan, baseline_task_ctx).await.unwrap()
+    })
+    .await;
 
-    let (_, baseline_max_rss) =
-        measure_max_rss(|| async { df_small.collect().await.unwrap() }).await;
+    let execution_plan = Arc::clone(&physical_plan);
+    let execution_task_ctx = ctx.task_ctx();
+    let (_, max_rss) = measure_max_rss(|| async move {
+        collect(execution_plan, execution_task_ctx).await.unwrap()
+    })
+    .await;
 
-    let (_, max_rss) = measure_max_rss(|| async { df.collect().await.unwrap() 
}).await;
+    if let (Some(operator), Some(expect_spill)) =
+        (expected_operator_name, expected_operator_spill)
+    {
+        let spill_count = operator_spill_count(physical_plan.as_ref(), 
operator);
+        assert_eq!(
+            spill_count > 0,
+            expect_spill,
+            "unexpected spill_count={spill_count} for {operator}",
+        );
+    }
 
     println!(
         "Memory before: {}, Memory after: {}",
diff --git a/datafusion/sqllogictest/test_files/sort_merge_join_spill.slt 
b/datafusion/sqllogictest/test_files/sort_merge_join_spill.slt
new file mode 100644
index 0000000000..1a3dcafa60
--- /dev/null
+++ b/datafusion/sqllogictest/test_files/sort_merge_join_spill.slt
@@ -0,0 +1,252 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# End-to-end SortMergeJoinExec spilling tests.
+#
+# Each query runs as an unlimited-memory hash join for expected results, then 
as
+# a memory-limited sort-merge join that must spill.
+
+hash-threshold 100
+
+# Use multiple partitions so the planner can select SortMergeJoinExec.
+statement ok
+SET datafusion.execution.target_partitions = 2
+
+statement ok
+SET datafusion.execution.batch_size = 200
+
+# Probe rows include one matching key; x=500 yields true, false, and NULL 
filters.
+statement ok
+CREATE VIEW probe AS
+SELECT value AS k, 500 AS x FROM generate_series(1, 3);
+
+# Probe rows with no matching buffered key.
+statement ok
+CREATE VIEW probe_nomatch AS SELECT value AS k FROM generate_series(7, 9);
+
+# One 2,000-row key group with a 512-byte payload, split into 10 batches.
+# Ordered generation avoids input sorts so only the join buffers the payload;
+# x includes NULLs and values on both sides of 500.
+statement ok
+CREATE VIEW wide AS
+SELECT 2 AS k,
+       value AS v,
+       CASE WHEN value % 10 = 0 THEN cast(NULL AS BIGINT) ELSE value % 1000 
END AS x,
+       lpad(cast(value AS varchar), 512, 'x') AS p
+FROM generate_series(1, 2000);
+
+# Keep output narrow while retaining the payload in the buffered input.
+
+query TT
+EXPLAIN SELECT p.k, w.v, length(w.p) FROM probe p JOIN wide w ON p.k = w.k
+----
+<slt:ignore>HashJoinExec<slt:ignore>
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p JOIN wide w ON p.k = w.k
+----
+6000 values hashing to ae029ab21ba6942d04253c3eb1fafbee
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p LEFT JOIN wide w ON p.k = w.k
+----
+6006 values hashing to 352109cc65a61f6224bb027dbee60df5
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM wide w RIGHT JOIN probe p ON p.k = w.k
+----
+6006 values hashing to 352109cc65a61f6224bb027dbee60df5
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p FULL JOIN wide w ON p.k = w.k
+----
+6006 values hashing to 352109cc65a61f6224bb027dbee60df5
+
+# Use the unlimited-memory hash join as the reference for filtered joins.
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p
+JOIN wide w ON p.k = w.k AND p.x < w.x
+----
+2700 values hashing to 824832563a1e34fe419885d0b7cccc9d
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p
+LEFT JOIN wide w ON p.k = w.k AND p.x < w.x
+----
+2706 values hashing to 38c8a5628a41b463e41d592c2401ca8d
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM wide w
+RIGHT JOIN probe p ON p.k = w.k AND p.x < w.x
+----
+2706 values hashing to 38c8a5628a41b463e41d592c2401ca8d
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p
+FULL JOIN wide w ON p.k = w.k AND p.x < w.x
+----
+6006 values hashing to b6b875b2658ee19e8bca7cd6e993dee1
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe_nomatch p FULL JOIN wide w ON p.k = w.k
+----
+6009 values hashing to 126cd87356bc636448b65c5fb5f4bd2b
+
+# A 64 KB pool spills all 10 buffered batches; each result must match its
+# unlimited-memory hash-join reference.
+
+statement ok
+SET datafusion.optimizer.prefer_hash_join = false
+
+statement ok
+SET datafusion.runtime.memory_limit = '64K'
+
+query TT
+EXPLAIN ANALYZE
+SELECT p.k, w.v, length(w.p) FROM probe p JOIN wide w ON p.k = w.k
+----
+Plan with Metrics
+<slt:ignore>SortMergeJoinExec: join_type=Inner, on=[(k@0, k@0)], 
metrics=[output_rows=2.00 K,<slt:ignore>spill_count=10, 
spilled_bytes=<slt:ignore>spilled_rows=2.00 K, peak_mem_used=<slt:ignore>
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p JOIN wide w ON p.k = w.k
+----
+6000 values hashing to ae029ab21ba6942d04253c3eb1fafbee
+
+query TT
+EXPLAIN ANALYZE
+SELECT p.k, w.v, length(w.p) FROM probe p LEFT JOIN wide w ON p.k = w.k
+----
+Plan with Metrics
+<slt:ignore>SortMergeJoinExec: join_type=Left, on=[(k@0, k@0)], 
metrics=[output_rows=2.00 K,<slt:ignore>spill_count=10, 
spilled_bytes=<slt:ignore>spilled_rows=2.00 K, peak_mem_used=<slt:ignore>
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p LEFT JOIN wide w ON p.k = w.k
+----
+6006 values hashing to 352109cc65a61f6224bb027dbee60df5
+
+query TT
+EXPLAIN ANALYZE
+SELECT p.k, w.v, length(w.p) FROM wide w RIGHT JOIN probe p ON p.k = w.k
+----
+Plan with Metrics
+<slt:ignore>SortMergeJoinExec: join_type=Right, on=[(k@0, k@0)], 
metrics=[output_rows=2.00 K,<slt:ignore>spill_count=10, 
spilled_bytes=<slt:ignore>spilled_rows=2.00 K, peak_mem_used=<slt:ignore>
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM wide w RIGHT JOIN probe p ON p.k = w.k
+----
+6006 values hashing to 352109cc65a61f6224bb027dbee60df5
+
+query TT
+EXPLAIN ANALYZE
+SELECT p.k, w.v, length(w.p) FROM probe p FULL JOIN wide w ON p.k = w.k
+----
+Plan with Metrics
+<slt:ignore>SortMergeJoinExec: join_type=Full, on=[(k@0, k@0)], 
metrics=[output_rows=2.00 K,<slt:ignore>spill_count=10, 
spilled_bytes=<slt:ignore>spilled_rows=2.00 K, peak_mem_used=<slt:ignore>
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p FULL JOIN wide w ON p.k = w.k
+----
+6006 values hashing to 352109cc65a61f6224bb027dbee60df5
+
+# Filtered spills cover true, false, and NULL masks; outer joins defer 
unmatched
+# rows until the whole key group is restored.
+
+query TT
+EXPLAIN ANALYZE
+SELECT p.k, w.v, length(w.p) FROM probe p
+JOIN wide w ON p.k = w.k AND p.x < w.x
+----
+Plan with Metrics
+<slt:ignore>SortMergeJoinExec: join_type=Inner, on=[(k@0, k@0)], filter=x@0 < 
x@1, metrics=[output_rows=900,<slt:ignore>spill_count=10, 
spilled_bytes=<slt:ignore>spilled_rows=2.00 K, peak_mem_used=<slt:ignore>
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p
+JOIN wide w ON p.k = w.k AND p.x < w.x
+----
+2700 values hashing to 824832563a1e34fe419885d0b7cccc9d
+
+query TT
+EXPLAIN ANALYZE
+SELECT p.k, w.v, length(w.p) FROM probe p
+LEFT JOIN wide w ON p.k = w.k AND p.x < w.x
+----
+Plan with Metrics
+<slt:ignore>SortMergeJoinExec: join_type=Left, on=[(k@0, k@0)], filter=x@0 < 
x@1, metrics=[output_rows=902,<slt:ignore>spill_count=10, 
spilled_bytes=<slt:ignore>spilled_rows=2.00 K, peak_mem_used=<slt:ignore>
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p
+LEFT JOIN wide w ON p.k = w.k AND p.x < w.x
+----
+2706 values hashing to 38c8a5628a41b463e41d592c2401ca8d
+
+query TT
+EXPLAIN ANALYZE
+SELECT p.k, w.v, length(w.p) FROM wide w
+RIGHT JOIN probe p ON p.k = w.k AND p.x < w.x
+----
+Plan with Metrics
+<slt:ignore>SortMergeJoinExec: join_type=Right, on=[(k@0, k@0)], filter=x@1 < 
x@0, metrics=[output_rows=902,<slt:ignore>spill_count=10, 
spilled_bytes=<slt:ignore>spilled_rows=2.00 K, peak_mem_used=<slt:ignore>
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM wide w
+RIGHT JOIN probe p ON p.k = w.k AND p.x < w.x
+----
+2706 values hashing to 38c8a5628a41b463e41d592c2401ca8d
+
+query TT
+EXPLAIN ANALYZE
+SELECT p.k, w.v, length(w.p) FROM probe p
+FULL JOIN wide w ON p.k = w.k AND p.x < w.x
+----
+Plan with Metrics
+<slt:ignore>SortMergeJoinExec: join_type=Full, on=[(k@0, k@0)], filter=x@0 < 
x@1, metrics=[output_rows=2.00 K,<slt:ignore>spill_count=10, 
spilled_bytes=<slt:ignore>spilled_rows=2.00 K, peak_mem_used=<slt:ignore>
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe p
+FULL JOIN wide w ON p.k = w.k AND p.x < w.x
+----
+6006 values hashing to b6b875b2658ee19e8bca7cd6e993dee1
+
+# Full join restores all buffered batches to emit unmatched rows.
+
+query TT
+EXPLAIN ANALYZE
+SELECT p.k, w.v, length(w.p) FROM probe_nomatch p FULL JOIN wide w ON p.k = w.k
+----
+Plan with Metrics
+<slt:ignore>SortMergeJoinExec: join_type=Full, on=[(k@0, k@0)], 
metrics=[output_rows=2.00 K,<slt:ignore>spill_count=10, 
spilled_bytes=<slt:ignore>spilled_rows=2.00 K, peak_mem_used=<slt:ignore>
+
+query III rowsort
+SELECT p.k, w.v, length(w.p) FROM probe_nomatch p FULL JOIN wide w ON p.k = w.k
+----
+6009 values hashing to 126cd87356bc636448b65c5fb5f4bd2b
+
+statement ok
+RESET datafusion.runtime.memory_limit
+
+statement ok
+RESET datafusion.optimizer.prefer_hash_join
+
+statement ok
+RESET datafusion.execution.batch_size
+
+statement ok
+SET datafusion.execution.target_partitions = 4
+
+statement ok
+RESET datafusion.catalog.create_default_catalog_and_schema


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to