Commits on Aug 22, 2026
-
Fix CPU cost of right-semi and right-anti hash joins
final_cost_hashjoin() assumed that the rows a hash join produces come from the outer side. That does not hold for JOIN_RIGHT_SEMI and JOIN_RIGHT_ANTI, which produce rows from the inner side: matched inner rows for the one, unmatched inner rows for the other. hashjointuples, which carries both the cpu_tuple_cost charge and the cost of the join clauses not used for hashing, was therefore counting the wrong rows, and could be far off in either direction. Compute hashjointuples from the inner side for these two join types, mirroring what JOIN_SEMI and JOIN_ANTI already do with the outer side. The fraction we need is semifactors.outer_match_frac: it is derived from the SpecialJoinInfo, so despite its name it always describes the semijoin's left-hand side, which is the inner side here. Compute the semijoin factors for these join types in all cases; previously that happened only when the inner side was provably unique. The same mix-up affects outer_matched_rows when the inner side is known unique, where the outer row count was multiplied by the inner side's match fraction. A unique inner side means each outer row has at most one match, so use the number of matching pairs instead. A right anti join evaluates the join clauses not used for hashing once per tuple passing the hash clauses, without any short-circuit. Charge those clauses on the matching-pair count, and only cpu_tuple_cost on the emitted rows. A right semi join short-circuits already-matched inner tuples and keeps the emitted-row charge, as JOIN_SEMI does. Nestloop and mergejoin need no equivalent fix: neither supports JOIN_RIGHT_SEMI, nestloop doesn't support JOIN_RIGHT_ANTI either, and final_cost_mergejoin() takes its count from approx_tuple_count(), which multiplies the two input sizes together and so gives the same answer whichever side is on the outside.