On Fri, Aug 21, 2026 at 4:55 PM wenhui qiu <[email protected]> wrote: >> He points out that costing joinrestrictinfo “returns the costs for ALL the >> quals”, and that after subtracting hash_qual_cost, qp_qual_cost represents >> all additional clauses beside the hash clauses.
That's true. qp_qual_cost here is the cost of joinqual + otherqual. >> So in this case qp_qual_cost combines two executor qual classes with >> different evaluation populations: non-hash joinquals and pushed-down >> otherquals. It therefore seems that changing the multiplier for the combined >> qp_qual_cost cannot in general make both parts correct. >> >> Would it make sense to split those two costs before applying the right-anti >> candidate-pair multiplier? I don't feel much motivated to do that. First, pushed-down quals that stick at a right anti join are close to nonexistent. Only the inner side's Vars are visible above the join, and the join nulls nothing, so a clause that could be placed there normally gets pushed down further and evaluated below the join. The comment in v4 notes this tradeoff: + * ... (This overcharges + * any pushed-down clauses, which are evaluated just once per emitted row, + * but such clauses are rare at a right anti join.) Second, nothing else in the join costing code makes this distinction. For plain joins, hashjointuples is the count before the joinquals are applied, so the pushed-down quals are overcharged in the same way. That's what the existing comment "(This is pessimistic since not all of the quals may get evaluated at each tuple.)" refers to. JOIN_SEMI and JOIN_ANTI have the same conflation in the other direction. If we really want to separate the two classes, it should not be limited to right anti joins; we'd need to do it for all join types, and that's beyond the scope of this patch. - Richard
