comphead commented on code in PR #25467:
URL: https://github.com/apache/datafusion/pull/25467#discussion_r4048193912


##########
datafusion/physical-plan/src/joins/piecewise_merge_join/exec.rs:
##########
@@ -320,14 +320,21 @@ impl PiecewiseMergeJoinExec {
         join_type: JoinType,
         num_partitions: usize,
     ) -> Result<Self> {
-        // Semi/Anti joins are handled by the existence streams; Mark joins 
are not
-        // supported yet.
-        if is_existence_join(join_type) && 
!is_supported_existence_join(join_type) {
-            return not_impl_err!(
-                "Existence join {join_type} is currently not supported for 
PiecewiseMergeJoin"
-            );
-        }
-
+        // There is no `null_aware` parameter here, unlike 
`HashJoinExec::try_new`: this
+        // constructor cannot express a null-aware mark join (see 
`JoinType::LeftMark`'s
+        // scalar-`NOT IN` variant, where `mark` is nullable), and 
`mark_streamed_batch`/
+        // `emit_matched` always build a non-nullable `mark` column. That is 
only sound
+        // because a null-aware mark join can never reach here: 
`decorrelate_predicate_subquery`
+        // only sets `null_aware` when the whole predicate is pure 
hash-equality with no
+        // residual (`mark_filter_is_hashable_only`), which means `join_on` is 
always
+        // non-empty for one -- and the PWMJ branch in `physical_planner.rs` 
only ever
+        // constructs this exec when `join_on` is empty. If either side of 
that ever changes
+        // (the PWMJ gate growing to accept a residual equijoin condition 
alongside a range
+        // predicate -- see the `TODO` on that branch -- or decorrelation 
producing
+        // `null_aware` from something other than a pure-equality predicate), 
this invariant
+        // breaks silently: a null-aware mark join would compute a plain 
boolean `mark` where
+        // SQL requires `NULL` (`UNKNOWN`), and nothing here would notice.
+        //

Review Comment:
   This comment's central claim is false. It says that if the invariant breaks, 
"a null-aware mark join would compute a plain boolean `mark` where SQL requires 
`NULL` ... and nothing here would notice." The planner already notices, 
unconditionally:
   
   ```rust
   // physical_planner.rs:1609-1614
   if *null_aware && join_on.is_empty() {
       return plan_err!(
           "null_aware {join_type} join requires equi-join keys, but the join 
has none"
       );
   }
   ```
   
   That runs before the `let join = if join_on.is_empty() { ... }` branch that 
constructs this exec, so for `null_aware == true` either `join_on` is non-empty 
(and the PWMJ branch is not taken) or it is empty (and planning fails). PWMJ 
cannot see a null-aware join, by explicit guard rather than by emergent 
reasoning.
   
   So the 16 lines reconstructing `decorrelate_predicate_subquery`'s behavior 
are both unnecessary and misleading: they tell a maintainer the property is 
unguarded when it is enforced five statements earlier in the same function. Two 
lines pointing at that guard carry the whole argument, and stay true if 
decorrelation changes.



##########
datafusion/physical-plan/src/joins/piecewise_merge_join/utils.rs:
##########
@@ -20,35 +20,13 @@ use datafusion_expr::JoinType;
 // Returns boolean for whether the join is a right existence join served by
 // `RightExistencePWMJStream`, which reads nothing but a single min/max off 
the buffered side.
 //
-// `RightMark` is deliberately excluded even though it is a right existence 
join: it needs the
-// buffered side walked in order and an extra boolean column, so it must not 
inherit this
-// stream's relaxed input requirements if the `try_new` gate is ever loosened.
+// `RightMark` belongs here too: deciding its mark column is the same one-key 
comparison as
+// `RightSemi`/`RightAnti`, just kept instead of used to filter, so it needs 
no more of the
+// buffered side than they do.
 pub(super) fn is_supported_right_existence_join(join_type: JoinType) -> bool {

Review Comment:
   With `is_existence_join` and `is_supported_existence_join` gone, "supported" 
no longer distinguishes anything: every existence join is supported now. The 
name reads as though some right existence join is still rejected. 
`is_right_existence_join` says what it tests.



##########
datafusion/proto/tests/cases/plans/joins.rs:
##########
@@ -1079,3 +1062,89 @@ async fn roundtrip_planned_piecewise_merge_join() -> 
Result<()> {
     }
     Ok(())
 }
+
+/// `roundtrip_test`/`roundtrip_test_and_return` only compare the `Debug` 
string of the
+/// before/after plans -- which, per their own doc comment, "often isn't 
sufficient to
+/// guarantee that no information is lost during serde because the string 
representation of
+/// a plan often only shows a subset of state". `LeftMark`/`RightMark` add no 
new field to
+/// encode (`join_type` already selects them from the shared proto enum, see
+/// `join_type_to_proto`/`join_type_from_proto`), so the real risk is not a 
missing wire field
+/// but a decoded plan that behaves differently at execution time. This 
actually executes both
+/// the original and the roundtripped plan over real data and compares their 
output batches
+/// row for row, including the `mark` column.
+#[tokio::test]
+async fn roundtrip_piecewise_merge_join_mark_executes_correctly() -> 
Result<()> {

Review Comment:
   Drop this. `roundtrip_piecewise_merge_join` above already round-trips both 
Mark types, and its field-count assertion (4 for `LeftMark`, 3 for `RightMark`) 
is by itself proof that `join_type` survived the wire: no other join type 
produces those widths, and the exec derives everything else from the six 
constructor arguments inside `try_new`. Executing both Mark types over real 
data is what `fuzz_pwmj_matches_nested_loop` does, across randomized inputs, 
operators and partition counts, rather than one hand-built four-row case.
   
   The doc comment argues the Debug-string comparison is too weak, which is 
true in general, but the PR description also establishes there is no new wire 
field here. 75 lines and three new imports (`Int64Array`, `RecordBatch`, 
`MemorySourceConfig`) for the one bit the field-count assertion already pins.



##########
datafusion/sqllogictest/test_files/pwmj.slt:
##########
@@ -503,6 +503,148 @@ ORDER BY 1 NULLS FIRST;
 NULL
 1
 
+# ------------------------------------------------------------------
+# LeftMark via PiecewiseMergeJoin
+# ------------------------------------------------------------------
+
+# `LeftMark` has no SQL syntax of its own -- it is produced by decorrelating 
an EXISTS/IN
+# subquery that appears inside a disjunction, since the outer filter then 
needs the
+# subquery's match result (the `mark`) as a value rather than as a row filter. 
Same data and
+# correlation as the plain-EXISTS `LeftSemi` case above: no `t1.t1_id` is over 
100, so the
+# `OR` degenerates to the mark alone and the result is identical -- 11 is the 
one row whose
+# mark is `false` and is correctly excluded.
+query I
+SELECT t1.t1_id
+FROM join_t1 t1
+WHERE t1.t1_id > 100 OR EXISTS (SELECT 1 FROM join_t2 t2 WHERE t1.t1_id > 
t2.t2_id)
+ORDER BY 1;
+----
+22
+33
+44
+
+query TT
+EXPLAIN
+SELECT t1.t1_id
+FROM join_t1 t1
+WHERE t1.t1_id > 100 OR EXISTS (SELECT 1 FROM join_t2 t2 WHERE t1.t1_id > 
t2.t2_id)
+ORDER BY 1;
+----
+logical_plan
+01)Sort: t1.t1_id ASC NULLS LAST
+02)--Projection: t1.t1_id
+03)----Filter: t1.t1_id > Int32(100) OR __correlated_sq_1.mark
+04)------LeftMark Join:  Filter: t1.t1_id > __correlated_sq_1.t2_id
+05)--------SubqueryAlias: t1
+06)----------TableScan: join_t1 projection=[t1_id]
+07)--------SubqueryAlias: __correlated_sq_1
+08)----------SubqueryAlias: t2
+09)------------TableScan: join_t2 projection=[t2_id]
+physical_plan
+01)SortPreservingMergeExec: [t1_id@0 ASC NULLS LAST]
+02)--SortExec: expr=[t1_id@0 ASC NULLS LAST], preserve_partitioning=[true]
+03)----FilterExec: t1_id@0 > 100 OR mark@1, projection=[t1_id@0]
+04)------RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=1
+05)--------PiecewiseMergeJoin: operator=Gt, join_type=LeftMark, on=(t1_id > 
t2_id)
+06)----------SortExec: expr=[t1_id@0 ASC], preserve_partitioning=[false]
+07)------------DataSourceExec: partitions=1, partition_sizes=[1]
+08)----------DataSourceExec: partitions=1, partition_sizes=[1]
+
+# `PiecewiseMergeJoinExec::swap_inputs` is unimplemented (`todo!()`), and the 
physical
+# optimizer's statistics-driven swap subrule (`join_selection.rs`) only ever 
downcasts to
+# `HashJoinExec`/`CrossJoinExec`/`NestedLoopJoinExec` -- it does not consider
+# `PiecewiseMergeJoinExec` at all. So unlike the equijoin case in 
`mark_join_matrix.slt`
+# (where a size-skewed `LeftMark` on `HashJoinExec` gets swapped to 
`RightMark` with inputs
+# flipped), a size-skewed range mark join through PWMJ has nothing to trigger 
that swap and
+# must stay `LeftMark` regardless of which side is bigger. This pins that: if
+# `swap_inputs` is ever implemented and wired in, this plan changing to 
`RightMark` is the
+# signal to add real `RightMark` coverage here rather than an accidental 
behavior change.
+statement ok
+CREATE TABLE pwmj_mark_swap_l(k INT) AS VALUES (1);

Review Comment:
   This pins the absence of a rewrite that cannot fire. As the comment itself 
establishes, `join_selection.rs` only ever downcasts to 
`HashJoinExec`/`CrossJoinExec`/`NestedLoopJoinExec`, so no amount of size skew 
reaches `PiecewiseMergeJoinExec::swap_inputs`. The cost is a 1000-row 
`generate_series` table and a `DataSourceExec: partitions=4, 
partition_sizes=[1, 0, 0, 0]` pin that any change to `target_partitions` 
defaults or `generate_series` partitioning will break, for a signal that a 
`todo!()` nobody calls is still uncalled.
   
   If the concern is that implementing `swap_inputs` later silently produces a 
`RightMark` with no coverage, the durable place to state that is a comment on 
`swap_inputs` itself.



##########
datafusion/physical-plan/src/joins/piecewise_merge_join/exec.rs:
##########
@@ -705,8 +713,10 @@ impl ExecutionPlan for PiecewiseMergeJoinExec {
         match self.join_type {
             // Right existence joins never read a buffered *row*, only a 
single min/max over
             // the whole side, so they fold the buffered input away as it 
arrives instead of
-            // collecting it.
-            JoinType::RightSemi | JoinType::RightAnti => {
+            // collecting it. `RightMark` decides its `mark` column with the 
exact same
+            // comparison as `RightSemi`/`RightAnti` -- it just keeps every 
row instead of
+            // filtering by it -- so it takes the same path.
+            JoinType::RightSemi | JoinType::RightAnti | JoinType::RightMark => 
{

Review Comment:
   This hardcodes the right-existence set while `required_input_ordering`, 
`input_distribution_requirements`, and `benefits_from_input_partitioning` all 
derive it from `is_supported_right_existence_join`. Four sites, three sharing a 
predicate and one spelling it out.
   
   The two have to agree: this arm selects `RightExistencePWMJStream`, which 
reads the buffered side unordered and multi-partition, and the predicate is 
what asks for those relaxed requirements. If they drift, a join type gets 
routed to a stream whose input requirements were never requested, which is a 
wrong-answer bug rather than a compile error. Call the predicate here too.



##########
datafusion/physical-plan/src/joins/piecewise_merge_join/existence_join.rs:
##########
@@ -462,6 +476,16 @@ pub(super) fn extreme_key(values: &ArrayRef, descending: 
bool) -> Result<ArrayRe
     extreme.to_array_of_size(1)
 }
 
+/// Builds the `LeftMark` `mark` column from the watermark: `false` for the 
unmatched prefix
+/// `[0, min_marked)`, `true` for the matched suffix `[min_marked, len)` -- 
the same split
+/// `LeftSemi`/`LeftAnti` slice the buffered batch on, just kept as one column 
instead of used
+/// to drop rows.
+fn mark_column(len: usize, min_marked: usize) -> ArrayRef {
+    let mut mark = vec![false; len];
+    mark[min_marked..].fill(true);
+    Arc::new(BooleanArray::from(mark))

Review Comment:
   Once @jayzhan211's `BooleanBufferBuilder` form lands, this is four lines 
with one caller. Inline it into `emit_matched` and drop the helper plus its 
four-line doc comment, which restates the arm that calls it.
   
   Supporting their suggestion rather than repeating it: 
`BooleanBuffer::new_unset`/`BooleanBufferBuilder` is already the house pattern 
for exactly this in the sibling mark implementation, including 
`nested_loop_join.rs:3226` using 
`BooleanBuffer::new_unset(right_batch.num_rows())` for the same "no buffered 
match, mark everything false" case that `mark_streamed_batch` builds with 
`vec![false; n]`. The current `vec![false; len]` plus `fill(true)` plus 
bit-packing is three passes and a byte-per-row temporary where the builder is 
one pass.



##########
datafusion/physical-plan/src/joins/piecewise_merge_join/existence_join.rs:
##########
@@ -409,22 +412,33 @@ impl ExistencePWMJStream {
                 .load(AtomicOrdering::SeqCst)
                 .min(buffered_len);
 
-            let sliced = match self.join_type {
+            let (num_rows, columns) = match self.join_type {
                 JoinType::LeftSemi => {
-                    buffered_batch.slice(min_marked, buffered_len - min_marked)
+                    let sliced =
+                        buffered_batch.slice(min_marked, buffered_len - 
min_marked);
+                    (sliced.num_rows(), sliced.columns().to_vec())
+                }
+                // `LeftMark` keeps every buffered row -- nothing to slice -- 
and appends
+                // the watermark as a `mark` column instead of using it to 
drop rows.
+                JoinType::LeftMark => {
+                    let mut columns = buffered_batch.columns().to_vec();
+                    columns.push(mark_column(buffered_len, min_marked));
+                    (buffered_len, columns)
+                }
+                // `LeftAnti`: the unmarked prefix, which includes every 
null-keyed row --
+                // nulls sort first and the watermark never drops below the 
buffered null
+                // count.
+                _ => {

Review Comment:
   This is a three-way dispatch with two arms named and the third left as `_`, 
documented as `LeftAnti` in a comment. Spell it `JoinType::LeftAnti` and let a 
fourth join type reaching this stream be a compile error rather than silently 
taking the anti slice. Same shape as `filter_streamed_batch`'s `_ =>` for 
`RightAnti`, which is worth the same treatment.



##########
datafusion/sqllogictest/test_files/pwmj.slt:
##########
@@ -1236,6 +1466,130 @@ physical_plan
 05)--------DataSourceExec: partitions=1, partition_sizes=[1]
 06)------DataSourceExec: partitions=1, partition_sizes=[1]
 
+# `LeftMark` shares the exact same buffered-side comparator 
(`JoinKeyComparator`, sorted by
+# `required_input_ordering`) as `LeftSemi`/`LeftAnti` above -- only 
`emit_matched`'s final step
+# differs (slice vs. `mark` column) -- so this pins that a dictionary key 
marks correctly
+# through that shared path too. `>` : only 'e' (id 3) is above 'c', same 
outcome the `EXISTS`
+# query above already pinned for `LeftSemi`.
+query I
+SELECT l.id FROM ex_dict_l l
+WHERE l.id > 100 OR EXISTS (SELECT 1 FROM ex_dict_r r WHERE l.v > r.v) ORDER 
BY 1;
+----
+3
+
+# ------------------------------------------------------------------
+# Existence joins: LeftMark with List/LargeList/FixedSizeList keys
+# ------------------------------------------------------------------
+
+statement ok
+CREATE TABLE ex_list_l(id INT, v INT[]);
+
+statement ok
+INSERT INTO ex_list_l VALUES (1, [1]), (2, [2]), (3, [4]), (4, [5]);
+
+statement ok
+CREATE TABLE ex_list_r(v INT[]);
+
+statement ok
+INSERT INTO ex_list_r VALUES ([2]), ([4]);
+
+# `>` : `EXISTS r WHERE l.v > r.v` holds iff `l.v` beats the smallest `r.v` 
([2]), which only
+# [4] (id 3) and [5] (id 4) do.
+query I
+SELECT l.id FROM ex_list_l l
+WHERE l.id > 100 OR EXISTS (SELECT 1 FROM ex_list_r r WHERE l.v > r.v) ORDER 
BY 1;
+----
+3
+4
+
+# `LargeList`/`FixedSizeList` go through the same generic comparator as `List` 
above

Review Comment:
   These roughly 100 lines cover key types through a code path that cannot be 
key-type sensitive, and the PR says so twice. `pwmj.slt:1469`: "`LeftMark` 
shares the exact same buffered-side comparator (`JoinKeyComparator`...) as 
`LeftSemi`/`LeftAnti` above -- only `emit_matched`'s final step differs (slice 
vs. `mark` column)". That final step is `buffered_batch.columns().to_vec()` 
plus a boolean column, which cannot observe whether the key was `List`, 
`LargeList`, `FixedSizeList` or a dictionary. The comparator itself is already 
covered for all four types by the `LeftSemi`/`LeftAnti` cases above. Keep one 
nested-key `LeftMark` case as a smoke test and drop `LargeList`, 
`FixedSizeList` and the dictionary variant.
   
   Same argument shrinks the EXPLAIN volume. `pwmj.slt` goes from 25 to 32 
`query TT`, and five of the new ones pin the same shape (`FilterExec ... OR 
mark@1` over `ProjectionExec` over `PiecewiseMergeJoin: join_type=LeftMark`) 
with only table and operator names differing: here, at `1505`, and the 
multi-NULL (`1224`) and `-0.0` (`1308`) blocks, whose comments likewise say 
they re-pin outcomes already covered for `LeftSemi`/`LeftAnti`. Keep the one 
canonical `LeftMark` EXPLAIN near the top of the section and make the rest 
result-only. Every unrelated planner change currently has to update all of them.



##########
datafusion/sqllogictest/test_files/pwmj.slt:
##########
@@ -503,6 +503,148 @@ ORDER BY 1 NULLS FIRST;
 NULL
 1
 
+# ------------------------------------------------------------------
+# LeftMark via PiecewiseMergeJoin
+# ------------------------------------------------------------------
+
+# `LeftMark` has no SQL syntax of its own -- it is produced by decorrelating 
an EXISTS/IN
+# subquery that appears inside a disjunction, since the outer filter then 
needs the
+# subquery's match result (the `mark`) as a value rather than as a row filter. 
Same data and
+# correlation as the plain-EXISTS `LeftSemi` case above: no `t1.t1_id` is over 
100, so the
+# `OR` degenerates to the mark alone and the result is identical -- 11 is the 
one row whose
+# mark is `false` and is correctly excluded.
+query I
+SELECT t1.t1_id
+FROM join_t1 t1
+WHERE t1.t1_id > 100 OR EXISTS (SELECT 1 FROM join_t2 t2 WHERE t1.t1_id > 
t2.t2_id)
+ORDER BY 1;
+----
+22
+33
+44
+
+query TT
+EXPLAIN
+SELECT t1.t1_id
+FROM join_t1 t1
+WHERE t1.t1_id > 100 OR EXISTS (SELECT 1 FROM join_t2 t2 WHERE t1.t1_id > 
t2.t2_id)
+ORDER BY 1;
+----
+logical_plan
+01)Sort: t1.t1_id ASC NULLS LAST
+02)--Projection: t1.t1_id
+03)----Filter: t1.t1_id > Int32(100) OR __correlated_sq_1.mark
+04)------LeftMark Join:  Filter: t1.t1_id > __correlated_sq_1.t2_id
+05)--------SubqueryAlias: t1
+06)----------TableScan: join_t1 projection=[t1_id]
+07)--------SubqueryAlias: __correlated_sq_1
+08)----------SubqueryAlias: t2
+09)------------TableScan: join_t2 projection=[t2_id]
+physical_plan
+01)SortPreservingMergeExec: [t1_id@0 ASC NULLS LAST]
+02)--SortExec: expr=[t1_id@0 ASC NULLS LAST], preserve_partitioning=[true]
+03)----FilterExec: t1_id@0 > 100 OR mark@1, projection=[t1_id@0]
+04)------RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=1
+05)--------PiecewiseMergeJoin: operator=Gt, join_type=LeftMark, on=(t1_id > 
t2_id)
+06)----------SortExec: expr=[t1_id@0 ASC], preserve_partitioning=[false]
+07)------------DataSourceExec: partitions=1, partition_sizes=[1]
+08)----------DataSourceExec: partitions=1, partition_sizes=[1]
+
+# `PiecewiseMergeJoinExec::swap_inputs` is unimplemented (`todo!()`), and the 
physical
+# optimizer's statistics-driven swap subrule (`join_selection.rs`) only ever 
downcasts to
+# `HashJoinExec`/`CrossJoinExec`/`NestedLoopJoinExec` -- it does not consider
+# `PiecewiseMergeJoinExec` at all. So unlike the equijoin case in 
`mark_join_matrix.slt`
+# (where a size-skewed `LeftMark` on `HashJoinExec` gets swapped to 
`RightMark` with inputs
+# flipped), a size-skewed range mark join through PWMJ has nothing to trigger 
that swap and
+# must stay `LeftMark` regardless of which side is bigger. This pins that: if
+# `swap_inputs` is ever implemented and wired in, this plan changing to 
`RightMark` is the
+# signal to add real `RightMark` coverage here rather than an accidental 
behavior change.
+statement ok
+CREATE TABLE pwmj_mark_swap_l(k INT) AS VALUES (1);
+
+statement ok
+CREATE TABLE pwmj_mark_swap_r(k INT) AS SELECT * FROM generate_series(1, 1000);
+
+query TT
+EXPLAIN
+SELECT l.k
+FROM pwmj_mark_swap_l l
+WHERE l.k > 100000 OR EXISTS (SELECT 1 FROM pwmj_mark_swap_r r WHERE l.k > r.k)
+ORDER BY 1;
+----
+logical_plan
+01)Sort: l.k ASC NULLS LAST
+02)--Projection: l.k
+03)----Filter: l.k > Int32(100000) OR __correlated_sq_1.mark
+04)------LeftMark Join:  Filter: l.k > __correlated_sq_1.k
+05)--------SubqueryAlias: l
+06)----------TableScan: pwmj_mark_swap_l projection=[k]
+07)--------SubqueryAlias: __correlated_sq_1
+08)----------SubqueryAlias: r
+09)------------TableScan: pwmj_mark_swap_r projection=[k]
+physical_plan
+01)SortPreservingMergeExec: [k@0 ASC NULLS LAST]
+02)--SortExec: expr=[k@0 ASC NULLS LAST], preserve_partitioning=[true]
+03)----FilterExec: k@0 > 100000 OR mark@1, projection=[k@0]
+04)------PiecewiseMergeJoin: operator=Gt, join_type=LeftMark, on=(k > k)
+05)--------SortExec: expr=[k@0 ASC], preserve_partitioning=[false]
+06)----------DataSourceExec: partitions=1, partition_sizes=[1]
+07)--------DataSourceExec: partitions=4, partition_sizes=[1, 0, 0, 0]
+
+# `PiecewiseMergeJoinExec::try_new` has no `null_aware` parameter and always 
builds a
+# non-nullable `mark` column, which is only sound because a null-aware 
`LeftMark` join (the
+# one scalar `NOT IN` plans, per `JoinType::LeftMark`'s doc) can never reach 
the PWMJ branch:
+# `decorrelate_predicate_subquery` only sets `null_aware` for a pure 
hash-equality predicate,
+# which always has a non-empty `join_on`, while PWMJ only activates when 
`join_on` is empty.
+# This pins the query that null-aware coverage in `null_aware_mark_join.slt` 
already uses
+# (with `enable_piecewise_merge_join` on, unlike that file) and asserts it 
still plans to
+# `HashJoinExec ... null_aware`, not `PiecewiseMergeJoin`: if that invariant 
is ever broken
+# (the PWMJ gate loosened to accept a residual equijoin condition, or 
decorrelation start
+# setting `null_aware` for something other than a pure-equality predicate), 
this is the
+# query that should end up on PWMJ and lose its nullable `mark` -- so this 
plan changing is
+# the signal, not a query that never had a chance to reach PWMJ in the first 
place.
+statement ok
+CREATE TABLE pwmj_null_aware_l(id INT) AS VALUES (1), (2), (NULL);

Review Comment:
   Redundant with the planner guard (see my comment on `exec.rs:323`). 
`physical_planner.rs:1609` turns a null-aware join with empty `join_on` into a 
`plan_err!`, so the outcome this block guards against cannot be reached, and 
the query pinned here is the one the comment admits `null_aware_mark_join.slt` 
already covers.
   
   The one thing it adds over that file is running with 
`enable_piecewise_merge_join = true` (which defaults to `false`). That is worth 
a sentence, not two tables, an EXPLAIN and a duplicated three-valued result 
assertion: the invariant lives in the `plan_err!`, and deleting that guard 
already fails its own tests.



-- 
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