Fix LIMIT in HASH_JOIN with recursive extend - #813
Conversation
2b043a8 to
0caaf0e
Compare
The design question: can we avoid
|
|
I see... IIUC the clankers are saying (mine agrees) that a FILTER invalidates every cap this optimizer can currently push, so traversal should stop right there, and there's no need to carry a flag over. I think (?) that makes sense, so I'll remove both my constructor flag and your method, and make FILTER an unconditional barrier. As to the other operators, I think it would be best to handle those separately. I'm already stepping a bit outside my comfy zone with this PR. |
Exactly. I'm imagining it to be a big switch statement where we evaluate every operator, not just The alternative: stateful visitor pattern could be hard to maintain and develop many corner cases over time. Some of it is inevitable (like using table stats to figure out if we want to push it down or not), but I want to make that call on a case-by-case basis by evaluating cost vs benefit. Commit 2bbec9a is an example of this (see |
I see. I'm afraid I don't have the bandwidth for this right now. What I can do is to simplify the change as I mentioned, I hope that's still useful. I have also added another test for a DISTINCT + SKIP case which has led me to muddy the waters a little bit with another fix (see updated PR description). |
Only if all the following hold: 1. A finite LIMIT exists. SKIP without LIMIT is not pushed. 2. No FILTER has been crossed between the LIMIT and the HASH_JOIN. 3. The hash join is an INNER join. 4. LogicalHashJoin::requireFlatProbeKeys() returns false (the join cannot multiply probe-side rows) 5. The probe-side shape is exactly PATH_PROPERTY_PROBE -> RECURSIVE_EXTEND. 6. skipNumber + limitNumber cannot overflow common::INVALID_LIMIT.
…ag (additive) Add a treatFilterAsBarrier toggle so the FILTER-terminated push-down can be turned on/off without removing code. Default (true) keeps the new barrier behavior the PR tests rely on; passing false restores the prior descent- through-FILTER behavior. The original constructor, FILTER comment, and legacy descent path are preserved verbatim; only the internal UNION_ALL construction gains the flag so sub-branch optimizers honor the same mode.
…wnSupported Factor the row-count-preserving pass-through operators (MULTIPLICITY_REDUCER, EXPLAIN, ACCUMULATE, PROJECTION) into a single isPushDownSupported() predicate, mirroring order_by_push_down_optimizer.cpp. FILTER and every other operator that can discard rows now become barriers by default instead of needing per-case exceptions. Also guard TABLE_FUNCTION_CALL against SKIP-only caps and skip+limit overflow, the same bug class previously fixed for DISTINCT and the recursive-extend join.
5da7f34 to
267cbc6
Compare
|
Thank you for the contribution! |
LIMITpushdown can currently cross a query hash join above recursive extend without proving that rows can survive the join. ALIMIT 5might stop recursive expansion after five candidate rows, but if a hash join or filter rejects one of them, the query will return only four rows even though more matching rows exist.This PR pushes down a finite, overflow-safe
SKIP+LIMITonly through the direct one-to-one inner rejoin generated for recursive extend. Intervening filters and all other hash joins block this pushdown, allowing recursion to continue until enough rows reach the outer limit.SKIPwithoutLIMITdoes not create a recursive cap.I added several regression tests to cover the original join failure, filtered paths, and
SKIPqueries.Update: Another test for a DISTINCT + SKIP case:
Tinysnb contains seven distinct ages, so the correct result contains five rows. However, the optimizer was doing this:
SKIP 2.INVALID_LIMIT.LogicalDistinctwith the skip value anyway.mapDistinctcalculate a physical cap from the available fields.mapDistinctstarted its calculation at zero:cap = 0no finite
LIMIT, so add nothingSKIPis 2, socap = 2The physical
DISTINCTstopped after collecting two distinct values. The real outer SKIP then discarded those two values:DISTINCT produces 2 rows + SKIP discards 2 rows => result contains 0 rows 💥