kumarUjjawal commented on code in PR #24955:
URL: https://github.com/apache/datafusion/pull/24955#discussion_r3940435490


##########
datafusion/core/benches/pwmj_semi_anti_sql.rs:
##########
@@ -472,5 +518,180 @@ fn bench_pwmj_semi_anti_sql(c: &mut Criterion) {
     group.finish();
 }
 
-criterion_group!(benches, bench_pwmj_semi_anti_sql);
+/// A `JoinFilter` for `lhs.key < rhs.key`, the same range relation every case 
in this file
+/// uses, for the `NestedLoopJoinExec` arm below -- which needs one built by 
hand since it is
+/// constructed directly rather than planned from SQL.
+fn key_lt_key_filter(s: &SchemaRef) -> JoinFilter {
+    let expr = Arc::new(BinaryExpr::new(
+        Arc::new(Column::new("key", 0)),
+        Operator::Lt,
+        Arc::new(Column::new("key", 1)),
+    )) as _;
+    let column_indices = vec![
+        ColumnIndex {
+            index: 0,
+            side: JoinSide::Left,
+        },
+        ColumnIndex {
+            index: 0,
+            side: JoinSide::Right,
+        },
+    ];
+    let key_field = s.field_with_name("key").unwrap().clone();
+    let intermediate_schema = Schema::new(vec![key_field.clone(), key_field]);
+    JoinFilter::new(expr, column_indices, Arc::new(intermediate_schema))
+}
+
+/// `RightMark` has no SQL surface (see the module doc's "Mark joins" 
section), so both arms
+/// are hand-built here instead of planned from SQL text: 
`PiecewiseMergeJoinExec` and
+/// `NestedLoopJoinExec`, over the same data, the same `lhs.key < rhs.key` 
relation, and the
+/// same join type -- with no risk of comparing an operator against itself, 
since which
+/// operator each arm uses is fixed by construction rather than read back off 
a plan.
+///
+/// Neither arm needs a `SortExec`: `RightMark`, like `RightSemi`/`RightAnti`, 
folds the
+/// buffered side to one key regardless of its order, and `NestedLoopJoinExec` 
never needs
+/// either side ordered.
+fn bench_pwmj_right_mark_hand_built(c: &mut Criterion) {
+    let rt = Runtime::new().unwrap();
+    let s = schema();
+    let ctx = SessionContext::new();
+
+    // Every other case in this file falls back to `NestedLoopJoinExec` 
through the planner
+    // itself when a build does not yet support the join type -- there is no 
such fallback
+    // here, since `RightMark` has no SQL surface to plan through in the first 
place. Probe
+    // `try_new` directly and skip the whole group rather than panic, so this 
benchmark stays
+    // runnable (as a no-op) against a build that has not merged `RightMark` 
support yet, and
+    // starts measuring on its own once that support lands.
+    if let Err(err) = PiecewiseMergeJoinExec::try_new(
+        Arc::new(EmptyExec::new(Arc::clone(&s))),
+        Arc::new(EmptyExec::new(Arc::clone(&s))),
+        (
+            Arc::new(Column::new("key", 0)) as _,
+            Arc::new(Column::new("key", 0)) as _,
+        ),
+        Operator::Lt,
+        JoinType::RightMark,
+        1,
+    ) {
+        println!(
+            "note: pwmj_vs_nlj_right_mark_hand_built skipped -- this build's \
+             PiecewiseMergeJoinExec does not support RightMark yet: {err}"
+        );
+        return;
+    }
+
+    let mut group = c.benchmark_group("pwmj_vs_nlj_right_mark_hand_built");
+    group.sample_size(10);
+
+    for (regime, right_offset, _fraction) in REGIMES {
+        let lhs_batches = build_batches(LEFT_ROWS, 0, &s);
+        let rhs_batches = build_batches(RIGHT_ROWS, right_offset, &s);
+
+        let pwmj_plan = {
+            let (lhs_batches, rhs_batches, s) =
+                (lhs_batches.clone(), rhs_batches.clone(), Arc::clone(&s));
+            move || -> Arc<dyn ExecutionPlan> {
+                let lhs = MemorySourceConfig::try_new_exec(
+                    std::slice::from_ref(&lhs_batches),
+                    Arc::clone(&s),
+                    None,
+                )
+                .unwrap();
+                let rhs = MemorySourceConfig::try_new_exec(
+                    std::slice::from_ref(&rhs_batches),
+                    Arc::clone(&s),
+                    None,
+                )
+                .unwrap();
+                Arc::new(
+                    PiecewiseMergeJoinExec::try_new(
+                        lhs,
+                        rhs,
+                        (
+                            Arc::new(Column::new("key", 0)) as _,
+                            Arc::new(Column::new("key", 0)) as _,
+                        ),
+                        Operator::Lt,
+                        JoinType::RightMark,
+                        1,
+                    )
+                    .unwrap(),
+                )
+            }
+        };
+        let nlj_plan = {
+            let (lhs_batches, rhs_batches, s) =
+                (lhs_batches.clone(), rhs_batches.clone(), Arc::clone(&s));
+            move || -> Arc<dyn ExecutionPlan> {
+                let lhs = MemorySourceConfig::try_new_exec(
+                    std::slice::from_ref(&lhs_batches),
+                    Arc::clone(&s),
+                    None,
+                )
+                .unwrap();
+                let rhs = MemorySourceConfig::try_new_exec(
+                    std::slice::from_ref(&rhs_batches),
+                    Arc::clone(&s),
+                    None,
+                )
+                .unwrap();
+                Arc::new(
+                    NestedLoopJoinExec::try_new(
+                        lhs,
+                        rhs,
+                        Some(key_lt_key_filter(&s)),
+                        &JoinType::RightMark,
+                        None,
+                    )
+                    .unwrap(),
+                )
+            }
+        };
+
+        // `RightMark` keeps every streamed row, matched or not, so both arms 
must return
+        // exactly `RIGHT_ROWS` regardless of the regime -- unlike 
`RightSemi`/`RightAnti`,
+        // where the regime changes the row count. The regime still matters to 
what is timed
+        // below: it changes how much of the comparison work each arm actually 
does (`mark`
+        // true vs false), even though the row count it returns cannot show 
that.
+        let pwmj_rows = run(pwmj_plan(), &ctx, &rt);
+        let nlj_rows = run(nlj_plan(), &ctx, &rt);
+        assert_eq!(

Review Comment:
   `run` returns only the number of rows, but `RightMark` always returns every 
row from the right input regardless of the match regime. Because `LEFT_ROWS == 
RIGHT_ROWS`, these assertions cannot even detect returning the wrong side. A 
PWMJ implementation that produces incorrect or uniformly false mark values 
would therefore pass these guards and could report misleading timings.
   
   Please inspect the materialized mark column before timing. Ideally compare 
PWMJ and NLJ results per input row; at minimum, compare their true/false/null 
counts and assert the expected all-match, no-match, and half-match 
distributions.



##########
datafusion/core/benches/pwmj_semi_anti_sql.rs:
##########
@@ -65,6 +66,26 @@
 //! The **same three key offsets** serve both halves, each read against the 
other side's
 //! extreme: a left row survives `EXISTS` iff `lhs.key < max(rhs.key)`, a 
right row survives
 //! `RIGHT SEMI` iff `min(lhs.key) < rhs.key`.
+//!
+//! ## Mark joins
+//! `LeftMark` reuses the exact same watermark-marking path as `LeftSemi` -- 
the only
+//! difference is the final pass, which appends a `mark` column instead of 
slicing the batch
+//! -- so it is folded into the same SQL sweep above via an `EXISTS` wrapped 
in an
+//! always-false `OR`, the one shape that decorrelates to it (see 
`Kind::LeftMark`'s doc). It
+//! needs no build-dependent handling beyond what `Kind::LeftMark` already 
gets from
+//! `PWMJ_OR_NLJ`: on a build that does not yet route `LeftMark` to PWMJ, the 
planner itself
+//! falls back to `NestedLoopJoinExec`, same as every other case here before 
its dependency
+//! landed.
+//!
+//! `RightMark` has no SQL surface at all: no keyword parses to it, and no 
optimizer rule

Review Comment:
   There is no direct SQL syntax for `RightMark`, but the physical optimizer 
can construct one indirectly: `JoinSelection` swaps a statistics-selected 
nested-loop join, and `NestedLoopJoinExec::swap_inputs` maps `LeftMark` to 
`RightMark` through `JoinType::swap`.
   
   Please narrow this to “no SQL syntax or logical optimizer rule emits 
RightMark directly” and describe this group as a deliberate direct-operator 
microbenchmark.



##########
datafusion/core/benches/pwmj_semi_anti_sql.rs:
##########
@@ -472,5 +518,180 @@ fn bench_pwmj_semi_anti_sql(c: &mut Criterion) {
     group.finish();
 }
 
-criterion_group!(benches, bench_pwmj_semi_anti_sql);
+/// A `JoinFilter` for `lhs.key < rhs.key`, the same range relation every case 
in this file
+/// uses, for the `NestedLoopJoinExec` arm below -- which needs one built by 
hand since it is
+/// constructed directly rather than planned from SQL.
+fn key_lt_key_filter(s: &SchemaRef) -> JoinFilter {
+    let expr = Arc::new(BinaryExpr::new(
+        Arc::new(Column::new("key", 0)),
+        Operator::Lt,
+        Arc::new(Column::new("key", 1)),
+    )) as _;
+    let column_indices = vec![
+        ColumnIndex {
+            index: 0,
+            side: JoinSide::Left,
+        },
+        ColumnIndex {
+            index: 0,
+            side: JoinSide::Right,
+        },
+    ];
+    let key_field = s.field_with_name("key").unwrap().clone();
+    let intermediate_schema = Schema::new(vec![key_field.clone(), key_field]);
+    JoinFilter::new(expr, column_indices, Arc::new(intermediate_schema))
+}
+
+/// `RightMark` has no SQL surface (see the module doc's "Mark joins" 
section), so both arms
+/// are hand-built here instead of planned from SQL text: 
`PiecewiseMergeJoinExec` and
+/// `NestedLoopJoinExec`, over the same data, the same `lhs.key < rhs.key` 
relation, and the
+/// same join type -- with no risk of comparing an operator against itself, 
since which
+/// operator each arm uses is fixed by construction rather than read back off 
a plan.
+///
+/// Neither arm needs a `SortExec`: `RightMark`, like `RightSemi`/`RightAnti`, 
folds the
+/// buffered side to one key regardless of its order, and `NestedLoopJoinExec` 
never needs
+/// either side ordered.
+fn bench_pwmj_right_mark_hand_built(c: &mut Criterion) {
+    let rt = Runtime::new().unwrap();
+    let s = schema();
+    let ctx = SessionContext::new();
+
+    // Every other case in this file falls back to `NestedLoopJoinExec` 
through the planner
+    // itself when a build does not yet support the join type -- there is no 
such fallback
+    // here, since `RightMark` has no SQL surface to plan through in the first 
place. Probe
+    // `try_new` directly and skip the whole group rather than panic, so this 
benchmark stays
+    // runnable (as a no-op) against a build that has not merged `RightMark` 
support yet, and
+    // starts measuring on its own once that support lands.
+    if let Err(err) = PiecewiseMergeJoinExec::try_new(

Review Comment:
   This branch converts every `try_new` error into a successful benchmark skip. 
Once RightMark support lands, an unrelated schema, operator, or invariant 
regression would silently remove the benchmark while reporting that the build 
merely lacks support.
   
   Please skip only the specific expected `NotImplemented` error and fail on 
every unexpected error. The compatibility skip can then be removed when the 
implementation lands.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to