Skip to content

Fix lookup join returning 0 rows when a dimension primary-key component is a literal [fj4WqyCCw3C5ShR1RfB7MoBPTpkRrBFYP1uT35g3MvT] - #19197

Open
waterWang wants to merge 1 commit into
apache:masterfrom
waterWang:fix/lookup-join-literal-key
Open

Fix lookup join returning 0 rows when a dimension primary-key component is a literal [fj4WqyCCw3C5ShR1RfB7MoBPTpkRrBFYP1uT35g3MvT]#19197
waterWang wants to merge 1 commit into
apache:masterfrom
waterWang:fix/lookup-join-literal-key

Conversation

@waterWang

Copy link
Copy Markdown

Description

When a lookup join condition includes a literal on a dimension table primary key column (e.g. dim_tbl.currency = 'gbp'), Calcite's analyzeCondition() classifies it as a non-equi condition rather than an equi-join key. The LookupJoinOperator builds the lookup key only from leftKeys (equi-join column-column pairs), so the literal component is missing from the key. The key becomes shorter than the dimension table's primary key, causing the lookup to always return null — producing 0 result rows.

Root Cause

The condition dim_tbl.currency = 'gbp' AND dim_tbl.rate_start_date = fact_tbl.rate_start_date is split by Calcite as:

  • Equi-join key: dim_tbl.rate_start_date = fact_tbl.rate_start_dateleftKeys/ rightKeys
  • Non-equi condition: dim_tbl.currency = 'gbp' (column vs literal)

The operator builds the key from leftKeys only, producing a 1-component key. But the dimension table's primary key is [currency, rate_start_date] (2 components), so the lookup always fails.

Fix

Build the lookup key in the dimension table's primary key column order, filling each position from either:

  • The corresponding left column (via equi-join leftKeys/ rightKeys mapping)
  • A literal value extracted from non-equi conditions of the form dim_col = literal

Testing

Added lookup_join_literal_key test case to LookupJoin.json with:

  • Dim table PK: [currency, rate_start_date]
  • Join: dim_tbl.currency = 'gbp' AND dim_tbl.rate_start_date = fact_tbl.rate_start_date
  • Expected: 1 result row ['gbp', 125]

Closes #19188

…nt is a literal

When a lookup join condition includes a literal on a dimension table primary key
column (e.g. dim_tbl.currency = 'gbp'), Calcite's analyzeCondition() classifies
it as a non-equi condition rather than an equi-join key. The LookupJoinOperator
builds the lookup key only from leftKeys (equi-join column-column pairs), so the
literal component is missing from the key, making it shorter than the dimension
table's primary key. The lookup always returns null, producing 0 result rows.

Fix: Build the lookup key in the dimension table's primary key column order.
For each primary key column, determine the value source: either the corresponding
left column (equi-join via leftKeys/rightKeys) or a literal value extracted from
the non-equi condition.

Closes apache#19188
@Jackie-Jiang Jackie-Jiang added bug Something is not working as expected query Related to query processing multi-stage Related to the multi-stage query engine labels Aug 10, 2026
@Jackie-Jiang
Jackie-Jiang requested review from gortiz and yashmayya and a lite review from Copilot August 10, 2026 18:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes lookup-join key construction when a dimension-table primary key component is provided as a literal in the join condition (e.g. dim.currency = 'gbp'), by building the lookup PrimaryKey in the dimension PK column order and populating missing components from literal non-equi predicates.

Changes:

  • Build lookup keys in dimension PK order, filling each PK component from either equi-join mappings or extracted dim_col = literal predicates.
  • Add a regression test case covering a literal PK component in the lookup join condition.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LookupJoinOperator.java Reworks lookup-key construction to include literal dimension PK components extracted from non-equi predicates.
pinot-query-runtime/src/test/resources/queries/LookupJoin.json Adds lookup_join_literal_key regression coverage for literal PK component joins.
Suppressed comments (1)

pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LookupJoinOperator.java:110

  • _rightKeyIds is being populated here but is never used later. The extra field and initialization add noise and should be removed.
    List<Integer> rightKeys = node.getRightKeys();
    _rightKeyIds = new int[rightKeys.size()];
    for (int i = 0; i < rightKeys.size(); i++) {
      _rightKeyIds[i] = rightKeys.get(i);
    }

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +123 to +126
List<String> primaryKeyColumns = _rightTable.getPrimaryKeyColumns();
Preconditions.checkState(primaryKeyColumns != null && !primaryKeyColumns.isEmpty(),
"Dimension table must have primary key columns for lookup join");
_keyLeftIndices = new int[primaryKeyColumns.size()];
Comment on lines 67 to +68
private final int[] _leftKeyIds;
private final int[] _rightKeyIds;
@@ -64,6 +65,12 @@ public class LookupJoinOperator extends MultiStageOperator {
private final LeafOperator _rightInput;
private final JoinRelType _joinType;
private final int[] _leftKeyIds;

@Jackie-Jiang Jackie-Jiang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @gortiz @yashmayya to help review the change

This should be quite rare, so we should try to make the overhead minimal for regular cases where no extra filter exists

for (RexExpression nonEquiCondition : nonEquiConditions) {
if (nonEquiCondition instanceof RexExpression.FunctionCall) {
RexExpression.FunctionCall functionCall = (RexExpression.FunctionCall) nonEquiCondition;
if ("EQUALS".equals(functionCall.getFunctionName()) && functionCall.getFunctionOperands().size() == 2) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not general enough. Essentially we want to support extra filters on looked-up records, and it is not limited to EQUALS

@codecov-commenter

codecov-commenter commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 49 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.90%. Comparing base (e51b4e4) to head (1a18c86).
⚠️ Report is 13 commits behind head on master.

Files with missing lines Patch % Lines
...not/query/runtime/operator/LookupJoinOperator.java 0.00% 49 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (e51b4e4) and HEAD (1a18c86). Click for more details.

HEAD has 4 uploads less than BASE
Flag BASE (e51b4e4) HEAD (1a18c86)
unittests1 1 0
unittests 2 1
java-25 5 4
temurin 5 4
Additional details and impacted files
@@              Coverage Diff              @@
##             master   #19197       +/-   ##
=============================================
- Coverage     66.65%   38.90%   -27.76%     
+ Complexity     1423     1422        -1     
=============================================
  Files          3443     3443               
  Lines        218632   218674       +42     
  Branches      34793    34808       +15     
=============================================
- Hits         145726    85069    -60657     
- Misses        61192   125843    +64651     
+ Partials      11714     7762     -3952     
Flag Coverage Δ
custom-integration1 100.00% <ø> (ø)
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 38.90% <0.00%> (-27.76%) ⬇️
temurin 38.90% <0.00%> (-27.76%) ⬇️
unittests 38.89% <0.00%> (-27.76%) ⬇️
unittests1 ?
unittests2 38.89% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something is not working as expected multi-stage Related to the multi-stage query engine query Related to query processing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lookup Join returns 0 rows when given a literal value

4 participants