yashmayya opened a new issue, #19229:
URL: https://github.com/apache/pinot/issues/19229
Two follow-ups raised by @timothy-e while reviewing #19210, deferred so that
the bug fix stayed reviewable. They are
independent of each other and can be split if that helps.
Background: #19210 makes `LookupJoinOperator` compile a key plan that maps
every dimension table primary key column to
its value source. Pass 1 fills a position from a join key. Pass 2 fills a
position that pass 1 left open from a
`dim_column = constant` condition.
## 1. Do not evaluate a condition after the lookup when the key already
enforces it
> Could we improve perf by following up with a change that allows us to
apply both filters during the hashmap lookup,
> to avoid materializing rows just to filter them out later?
One clarification on the premise: no row is materialized and then discarded
today. `JoinedRowView` is a lazy view over
the left and right rows, and `toArray` runs only after the filters pass. The
waste is the evaluator call and, in one
case, the map probe itself.
There are two distinct cases, and they want different treatments.
**A constant that pass 2 folded into the key.** The condition is then
guaranteed by the lookup, so evaluating it again
per row can only ever return true. `_nonEquiEvaluators` is built from every
entry of `JoinNode.getNonEquiConditions()`
before the key plan is compiled, and nothing removes the folded ones. This
hits the headline query from #19188, where
`dim.currency = 'gbp'` is the only non-equi condition and is fully consumed
by pass 2, so the `isEmpty()` fast path is
never taken. Fix: have `compileKeyPlan` report which condition indices it
consumed and skip those when building the
evaluators.
**A constant on a position that a join key already filled**, for example
`ON dim.currency = fact.currency AND dim.rate_start_date =
fact.rate_start_date AND dim.currency = 'gbp'`. Here the
condition is not redundant. It is the only thing enforcing the constant, and
#19210 deliberately keeps it as a filter
so that the join does not silently widen. It can still be applied earlier:
that key position reads `leftRow[i]`, so
`dim.currency = 'gbp'` is equivalent to `leftRow[i] = 'gbp'`, which can be
checked before the probe rather than after
it. That skips the map lookup for rows that cannot match.
Better still, the planner could infer `fact.currency = 'gbp'` and push it
into the fact table scan.
`PinotJoinPushTransitivePredicatesRule` does push left inferred predicates,
and only blocks the right side for lookup
joins, but it does not fire for this shape today. A test in #19210 relies on
that: the `usd` fact row still reaches the
join, which is what lets the test tell a correct implementation apart from
one that lets the constant overwrite the
join key. Worth understanding why the inference does not happen before
choosing between the planner fix and the
runtime one.
## 2. Accept join conditions that a post-lookup filter can satisfy
> Both of these seem like they could be added as filters after the join,
which would increase our SQL compatibility?
#19210 rejects two shapes that it could instead support:
- **A join key on a dimension column outside the primary key**, for example
`AND dim.rate = fact.amount`. The row
returned by the lookup already carries that column, because the right side
projects the whole dimension schema, so
the condition can run after the lookup as
`EQUALS(InputRef(leftColumnId), InputRef(leftColumnSize + rightColumnId))`
on the existing `TransformOperand` path.
- **More than one join key on the same primary key column**, for example `ON
dim.c = fact.a AND dim.c = fact.b`. The
first fills the key position and the rest become the same kind of
post-lookup filter.
Both are rejected today only because dropping the surplus condition would
silently return rows that do not match it,
which is worse than an error. A filter is better than either.
Left join semantics come out right without extra work: a row that finds a
primary key match but fails the extra filter
falls through to the existing null padding branch.
Two things to handle:
- Semi and anti joins project the left columns only, so their result schema
cannot address a dimension column. #19210
rejects a non-equi condition for those two join types for this reason.
Either keep rejecting these shapes for semi
and anti joins, or build their evaluators against a left plus right schema.
- Calcite records `filterNulls` per join key, but `JoinNode` does not carry
it, so a synthesized condition would use
`=` semantics. That matches what `HashJoinOperator` already does.
A primary key column that nothing determines must stay an error. There is no
point lookup to perform, so the only
alternatives are an error or a planner level fallback to a hash join.
## Not included
Two other points from the same review are already addressed in #19210: the
constant is no longer converted in the
operator, since the planner coerces the operands of a comparison and the
operator now checks that, and the operator
test now names its column ids. The `BYTES` primary key question became
#19228, because it is a dimension table
bug rather than a lookup join one.
--
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]