This is an automated email from the ASF dual-hosted git repository.

ccaominh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new accd710  Add equivalent test coverage for all RHS join impls (#9831)
accd710 is described below

commit accd710115efea87120d58f01b08342065d6ae5f
Author: Maytas Monsereenusorn <52679095+mayt...@users.noreply.github.com>
AuthorDate: Wed May 6 13:10:41 2020 -1000

    Add equivalent test coverage for all RHS join impls (#9831)
    
    * Add equivalent test coverage for all RHS join impls
    
    * address comments
---
 .../join/HashJoinSegmentStorageAdapterTest.java    | 606 ++++++++++++++++++++-
 .../druid/segment/join/JoinFilterAnalyzerTest.java | 419 ++++++++++++++
 .../segment/join/table/LookupJoinMatcherTest.java  | 169 ++++++
 3 files changed, 1177 insertions(+), 17 deletions(-)

diff --git 
a/processing/src/test/java/org/apache/druid/segment/join/HashJoinSegmentStorageAdapterTest.java
 
b/processing/src/test/java/org/apache/druid/segment/join/HashJoinSegmentStorageAdapterTest.java
index a9b02e2..b7e9a58 100644
--- 
a/processing/src/test/java/org/apache/druid/segment/join/HashJoinSegmentStorageAdapterTest.java
+++ 
b/processing/src/test/java/org/apache/druid/segment/join/HashJoinSegmentStorageAdapterTest.java
@@ -365,6 +365,74 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
   }
 
   @Test
+  public void test_makeCursors_factToCountryLeftUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingIsoCodeLookup(JoinType.LEFT));
+
+    JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
+        joinableClauses,
+        VirtualColumns.EMPTY,
+        null,
+        true,
+        true,
+        true,
+        QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
+    );
+
+    JoinTestHelper.verifyCursors(
+        new HashJoinSegmentStorageAdapter(
+            factSegment.asStorageAdapter(),
+            joinableClauses,
+            preAnalysis
+        ).makeCursors(
+            null,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of(
+            new Object[]{"Talk:Oswald Tilghman", null, null, null},
+            new Object[]{"Rallicula", null, null, null},
+            new Object[]{"Peremptory norm", "AU", "AU", "Australia"},
+            new Object[]{"Apamea abruzzorum", null, null, null},
+            new Object[]{"Atractus flammigerus", null, null, null},
+            new Object[]{"Agama mossambica", null, null, null},
+            new Object[]{"Mathis Bolly", "MX", "MX", "Mexico"},
+            new Object[]{"유희왕 GX", "KR", "KR", "Republic of Korea"},
+            new Object[]{"青野武", "JP", "JP", "Japan"},
+            new Object[]{"Golpe de Estado en Chile de 1973", "CL", "CL", 
"Chile"},
+            new Object[]{"President of India", "US", "US", "United States"},
+            new Object[]{"Diskussion:Sebastian Schulz", "DE", "DE", "Germany"},
+            new Object[]{"Saison 9 de Secret Story", "FR", "FR", "France"},
+            new Object[]{"Glasgow", "GB", "GB", "United Kingdom"},
+            new Object[]{"Didier Leclair", "CA", "CA", "Canada"},
+            new Object[]{"Les Argonautes", "CA", "CA", "Canada"},
+            new Object[]{"Otjiwarongo Airport", "US", "US", "United States"},
+            new Object[]{"Sarah Michelle Gellar", "CA", "CA", "Canada"},
+            new Object[]{"DirecTV", "US", "US", "United States"},
+            new Object[]{"Carlo Curti", "US", "US", "United States"},
+            new Object[]{"Giusy Ferreri discography", "IT", "IT", "Italy"},
+            new Object[]{"Roma-Bangkok", "IT", "IT", "Italy"},
+            new Object[]{"Wendigo", "SV", "SV", "El Salvador"},
+            new Object[]{"Алиса в Зазеркалье", "NO", "NO", "Norway"},
+            new Object[]{"Gabinete Ministerial de Rafael Correa", "EC", "EC", 
"Ecuador"},
+            new Object[]{"Old Anatolian Turkish", "US", "US", "United States"},
+            new Object[]{"Cream Soda", "SU", "SU", "States United"},
+            new Object[]{"Orange Soda", "MatchNothing", null, null},
+            new Object[]{"History of Fourems", "MMMM", "MMMM", "Fourems"}
+        )
+    );
+  }
+
+  @Test
   public void test_makeCursors_factToCountryInner()
   {
     List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryOnIsoCode(JoinType.INNER));
@@ -667,6 +735,46 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
   }
 
   @Test
+  public void test_makeCursors_factToCountryLeftWithFilterOnFactsUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingIsoCodeLookup(JoinType.LEFT));
+    Filter filter = new SelectorDimFilter("channel", "#de.wikipedia", 
null).toFilter();
+    JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
+        joinableClauses,
+        VirtualColumns.EMPTY,
+        filter,
+        true,
+        true,
+        true,
+        QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
+    );
+
+    JoinTestHelper.verifyCursors(
+        new HashJoinSegmentStorageAdapter(
+            factSegment.asStorageAdapter(),
+            joinableClauses,
+            preAnalysis
+        ).makeCursors(
+            filter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of(
+            new Object[]{"Diskussion:Sebastian Schulz", "DE", "DE", "Germany"}
+        )
+    );
+  }
+
+  @Test
   public void test_makeCursors_factToCountryRightWithFilterOnLeftIsNull()
   {
     List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryOnIsoCode(JoinType.RIGHT));
@@ -710,6 +818,48 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
   }
 
   @Test
+  public void 
test_makeCursors_factToCountryRightWithFilterOnLeftIsNullUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingIsoCodeLookup(JoinType.RIGHT));
+    Filter filter = new SelectorDimFilter("channel", null, null).toFilter();
+    JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
+        joinableClauses,
+        VirtualColumns.EMPTY,
+        filter,
+        true,
+        true,
+        true,
+        QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
+    );
+
+    JoinTestHelper.verifyCursors(
+        new HashJoinSegmentStorageAdapter(
+            factSegment.asStorageAdapter(),
+            joinableClauses,
+            preAnalysis
+        ).makeCursors(
+            filter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            "countryNumber",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of(
+            new Object[]{null, null, NullHandling.sqlCompatible() ? null : 0L, 
"AX", "Atlantis"},
+            new Object[]{null, null, NullHandling.sqlCompatible() ? null : 0L, 
"USCA", "Usca"}
+        )
+    );
+  }
+
+  @Test
   public void test_makeCursors_factToCountryFullWithFilterOnLeftIsNull()
   {
     List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryOnIsoCode(JoinType.FULL));
@@ -753,6 +903,48 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
   }
 
   @Test
+  public void 
test_makeCursors_factToCountryFullWithFilterOnLeftIsNullUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingIsoCodeLookup(JoinType.FULL));
+    Filter filter = new SelectorDimFilter("channel", null, null).toFilter();
+    JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
+        joinableClauses,
+        VirtualColumns.EMPTY,
+        filter,
+        true,
+        true,
+        true,
+        QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
+    );
+
+    JoinTestHelper.verifyCursors(
+        new HashJoinSegmentStorageAdapter(
+            factSegment.asStorageAdapter(),
+            joinableClauses,
+            preAnalysis
+        ).makeCursors(
+            filter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            "countryNumber",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of(
+            new Object[]{null, null, NullHandling.sqlCompatible() ? null : 0L, 
"AX", "Atlantis"},
+            new Object[]{null, null, NullHandling.sqlCompatible() ? null : 0L, 
"USCA", "Usca"}
+        )
+    );
+  }
+
+  @Test
   public void test_makeCursors_factToCountryRightWithFilterOnJoinable()
   {
     List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryOnIsoCode(JoinType.RIGHT));
@@ -800,6 +992,52 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
   }
 
   @Test
+  public void 
test_makeCursors_factToCountryRightWithFilterOnJoinableUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingIsoCodeLookup(JoinType.RIGHT));
+    Filter filter = new SelectorDimFilter(
+        FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v",
+        "Germany",
+        null
+    ).toFilter();
+
+    JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
+        joinableClauses,
+        VirtualColumns.EMPTY,
+        filter,
+        true,
+        true,
+        true,
+        QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
+    );
+
+    JoinTestHelper.verifyCursors(
+        new HashJoinSegmentStorageAdapter(
+            factSegment.asStorageAdapter(),
+            joinableClauses,
+            preAnalysis
+        ).makeCursors(
+            filter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            "countryNumber",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of(
+            new Object[]{"Diskussion:Sebastian Schulz", "DE", 3L, "DE", 
"Germany"}
+        )
+    );
+  }
+
+  @Test
   public void test_makeCursors_factToCountryLeftWithFilterOnJoinable()
   {
     List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryOnIsoCode(JoinType.LEFT));
@@ -976,17 +1214,33 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
   }
 
   @Test
-  public void test_makeCursors_factToRegionToCountryLeft()
+  public void 
test_makeCursors_factToCountryInnerWithFilterInsteadOfRealJoinConditionUsingLookup()
   {
+    // Join condition => always true.
+    // Filter => Fact to countries on countryIsoCode.
+
     List<JoinableClause> joinableClauses = ImmutableList.of(
-        factToRegion(JoinType.LEFT),
-        regionToCountry(JoinType.LEFT)
+        new JoinableClause(
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+            LookupJoinable.wrap(countryIsoCodeToNameLookup),
+            JoinType.INNER,
+            JoinConditionAnalysis.forExpression(
+                "1",
+                FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+                ExprMacroTable.nil()
+            )
+        )
     );
 
+    Filter filter = new ExpressionDimFilter(
+        StringUtils.format("\"%sk\" == countryIsoCode", 
FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX),
+        ExprMacroTable.nil()
+    ).toFilter();
+
     JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
         joinableClauses,
         VirtualColumns.EMPTY,
-        null,
+        filter,
         true,
         true,
         true,
@@ -999,7 +1253,7 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
             joinableClauses,
             preAnalysis
         ).makeCursors(
-            null,
+            filter,
             Intervals.ETERNITY,
             VirtualColumns.EMPTY,
             Granularities.ALL,
@@ -1008,20 +1262,85 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
         ),
         ImmutableList.of(
             "page",
-            FACT_TO_REGION_PREFIX + "regionName",
-            REGION_TO_COUNTRY_PREFIX + "countryName"
+            "countryIsoCode",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
         ),
         ImmutableList.of(
-            new Object[]{"Talk:Oswald Tilghman", null, null},
-            new Object[]{"Rallicula", null, null},
-            new Object[]{"Peremptory norm", "New South Wales", "Australia"},
-            new Object[]{"Apamea abruzzorum", null, null},
-            new Object[]{"Atractus flammigerus", null, null},
-            new Object[]{"Agama mossambica", null, null},
-            new Object[]{"Mathis Bolly", "Mexico City", "Mexico"},
-            new Object[]{"유희왕 GX", "Seoul", "Republic of Korea"},
-            new Object[]{"青野武", "Tōkyō", "Japan"},
-            new Object[]{"Golpe de Estado en Chile de 1973", "Santiago 
Metropolitan", "Chile"},
+            new Object[]{"Peremptory norm", "AU", "AU", "Australia"},
+            new Object[]{"Mathis Bolly", "MX", "MX", "Mexico"},
+            new Object[]{"유희왕 GX", "KR", "KR", "Republic of Korea"},
+            new Object[]{"青野武", "JP", "JP", "Japan"},
+            new Object[]{"Golpe de Estado en Chile de 1973", "CL", "CL", 
"Chile"},
+            new Object[]{"President of India", "US", "US", "United States"},
+            new Object[]{"Diskussion:Sebastian Schulz", "DE", "DE", "Germany"},
+            new Object[]{"Saison 9 de Secret Story", "FR", "FR", "France"},
+            new Object[]{"Glasgow", "GB", "GB", "United Kingdom"},
+            new Object[]{"Didier Leclair", "CA", "CA", "Canada"},
+            new Object[]{"Les Argonautes", "CA", "CA", "Canada"},
+            new Object[]{"Otjiwarongo Airport", "US", "US", "United States"},
+            new Object[]{"Sarah Michelle Gellar", "CA", "CA", "Canada"},
+            new Object[]{"DirecTV", "US", "US", "United States"},
+            new Object[]{"Carlo Curti", "US", "US", "United States"},
+            new Object[]{"Giusy Ferreri discography", "IT", "IT", "Italy"},
+            new Object[]{"Roma-Bangkok", "IT", "IT", "Italy"},
+            new Object[]{"Wendigo", "SV", "SV", "El Salvador"},
+            new Object[]{"Алиса в Зазеркалье", "NO", "NO", "Norway"},
+            new Object[]{"Gabinete Ministerial de Rafael Correa", "EC", "EC", 
"Ecuador"},
+            new Object[]{"Old Anatolian Turkish", "US", "US", "United States"},
+            new Object[]{"Cream Soda", "SU", "SU", "States United"},
+            new Object[]{"History of Fourems", "MMMM", "MMMM", "Fourems"}
+        )
+    );
+  }
+
+  @Test
+  public void test_makeCursors_factToRegionToCountryLeft()
+  {
+    List<JoinableClause> joinableClauses = ImmutableList.of(
+        factToRegion(JoinType.LEFT),
+        regionToCountry(JoinType.LEFT)
+    );
+
+    JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
+        joinableClauses,
+        VirtualColumns.EMPTY,
+        null,
+        true,
+        true,
+        true,
+        QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
+    );
+
+    JoinTestHelper.verifyCursors(
+        new HashJoinSegmentStorageAdapter(
+            factSegment.asStorageAdapter(),
+            joinableClauses,
+            preAnalysis
+        ).makeCursors(
+            null,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            FACT_TO_REGION_PREFIX + "regionName",
+            REGION_TO_COUNTRY_PREFIX + "countryName"
+        ),
+        ImmutableList.of(
+            new Object[]{"Talk:Oswald Tilghman", null, null},
+            new Object[]{"Rallicula", null, null},
+            new Object[]{"Peremptory norm", "New South Wales", "Australia"},
+            new Object[]{"Apamea abruzzorum", null, null},
+            new Object[]{"Atractus flammigerus", null, null},
+            new Object[]{"Agama mossambica", null, null},
+            new Object[]{"Mathis Bolly", "Mexico City", "Mexico"},
+            new Object[]{"유희왕 GX", "Seoul", "Republic of Korea"},
+            new Object[]{"青野武", "Tōkyō", "Japan"},
+            new Object[]{"Golpe de Estado en Chile de 1973", "Santiago 
Metropolitan", "Chile"},
             new Object[]{"President of India", "California", "United States"},
             new Object[]{"Diskussion:Sebastian Schulz", "Hesse", "Germany"},
             new Object[]{"Saison 9 de Secret Story", "Val d'Oise", "France"},
@@ -1348,6 +1667,72 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
   }
 
   @Test
+  public void test_makeCursors_factToCountryUsingVirtualColumnUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = ImmutableList.of(
+        new JoinableClause(
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+            LookupJoinable.wrap(countryIsoCodeToNameLookup),
+            JoinType.INNER,
+            JoinConditionAnalysis.forExpression(
+                StringUtils.format("\"%sk\" == virtual", 
FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX),
+                FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    VirtualColumns virtualColumns = VirtualColumns.create(
+        Collections.singletonList(
+            new ExpressionVirtualColumn(
+                "virtual",
+                "concat(substring(countryIsoCode, 0, 1),'L')",
+                ValueType.STRING,
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
+        joinableClauses,
+        virtualColumns,
+        null,
+        true,
+        true,
+        true,
+        QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
+    );
+
+    JoinTestHelper.verifyCursors(
+        new HashJoinSegmentStorageAdapter(
+            factSegment.asStorageAdapter(),
+            joinableClauses,
+            preAnalysis
+        ).makeCursors(
+            null,
+            Intervals.ETERNITY,
+            virtualColumns,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            "virtual",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of(
+            new Object[]{"Golpe de Estado en Chile de 1973", "CL", "CL", "CL", 
"Chile"},
+            new Object[]{"Didier Leclair", "CA", "CL", "CL", "Chile"},
+            new Object[]{"Les Argonautes", "CA", "CL", "CL", "Chile"},
+            new Object[]{"Sarah Michelle Gellar", "CA", "CL", "CL", "Chile"}
+        )
+    );
+  }
+
+  @Test
   public void test_makeCursors_factToCountryUsingExpression()
   {
     List<JoinableClause> joinableClauses = ImmutableList.of(
@@ -1405,6 +1790,63 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
   }
 
   @Test
+  public void test_makeCursors_factToCountryUsingExpressionUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = ImmutableList.of(
+        new JoinableClause(
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+            LookupJoinable.wrap(countryIsoCodeToNameLookup),
+            JoinType.INNER,
+            JoinConditionAnalysis.forExpression(
+                StringUtils.format(
+                    "\"%sk\" == concat(substring(countryIsoCode, 0, 1),'L')",
+                    FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX
+                ),
+                FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
+        joinableClauses,
+        VirtualColumns.EMPTY,
+        null,
+        true,
+        true,
+        true,
+        QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
+    );
+
+    JoinTestHelper.verifyCursors(
+        new HashJoinSegmentStorageAdapter(
+            factSegment.asStorageAdapter(),
+            joinableClauses,
+            preAnalysis
+        ).makeCursors(
+            null,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of(
+            new Object[]{"Golpe de Estado en Chile de 1973", "CL", "CL", 
"Chile"},
+            new Object[]{"Didier Leclair", "CA", "CL", "Chile"},
+            new Object[]{"Les Argonautes", "CA", "CL", "Chile"},
+            new Object[]{"Sarah Michelle Gellar", "CA", "CL", "Chile"}
+        )
+    );
+  }
+
+  @Test
   public void test_makeCursors_factToRegionTheWrongWay()
   {
     // Joins using only regionIsoCode, which is wrong since they are not 
unique internationally.
@@ -1514,6 +1956,52 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
   }
 
   @Test
+  public void test_makeCursors_errorOnNonEquiJoinUsingLookup()
+  {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Cannot join lookup with non-equi 
condition: x == y");
+
+    List<JoinableClause> joinableClauses = ImmutableList.of(
+        new JoinableClause(
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+            LookupJoinable.wrap(countryIsoCodeToNameLookup),
+            JoinType.LEFT,
+            JoinConditionAnalysis.forExpression(
+                "x == y",
+                FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
+        joinableClauses,
+        VirtualColumns.EMPTY,
+        null,
+        true,
+        true,
+        true,
+        QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
+    );
+
+    JoinTestHelper.readCursors(
+        new HashJoinSegmentStorageAdapter(
+            factSegment.asStorageAdapter(),
+            joinableClauses,
+            preAnalysis
+        ).makeCursors(
+            null,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of()
+    );
+  }
+
+  @Test
   public void test_makeCursors_errorOnNonKeyBasedJoin()
   {
     expectedException.expect(IllegalArgumentException.class);
@@ -1560,6 +2048,51 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
   }
 
   @Test
+  public void test_makeCursors_errorOnNonKeyBasedJoinUsingLookup()
+  {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Cannot join lookup with condition 
referring to non-key column: x == \"c1.countryName");
+    List<JoinableClause> joinableClauses = ImmutableList.of(
+        new JoinableClause(
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+            LookupJoinable.wrap(countryIsoCodeToNameLookup),
+            JoinType.LEFT,
+            JoinConditionAnalysis.forExpression(
+                StringUtils.format("x == \"%scountryName\"", 
FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX),
+                FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
+        joinableClauses,
+        VirtualColumns.EMPTY,
+        null,
+        true,
+        true,
+        true,
+        QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
+    );
+
+    JoinTestHelper.readCursors(
+        new HashJoinSegmentStorageAdapter(
+            factSegment.asStorageAdapter(),
+            joinableClauses,
+            preAnalysis
+        ).makeCursors(
+            null,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of()
+    );
+  }
+
+  @Test
   public void test_makeCursors_factToCountryLeft_filterExcludesAllLeftRows()
   {
     Filter originalFilter = new SelectorFilter("page", "this matches nothing");
@@ -1598,4 +2131,43 @@ public class HashJoinSegmentStorageAdapterTest extends 
BaseHashJoinSegmentStorag
         ImmutableList.of()
     );
   }
+
+  @Test
+  public void 
test_makeCursors_factToCountryLeft_filterExcludesAllLeftRowsUsingLookup()
+  {
+    Filter originalFilter = new SelectorFilter("page", "this matches nothing");
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingIsoCodeLookup(JoinType.LEFT));
+
+    JoinFilterPreAnalysis preAnalysis = 
JoinFilterAnalyzer.computeJoinFilterPreAnalysis(
+        joinableClauses,
+        VirtualColumns.EMPTY,
+        originalFilter,
+        true,
+        true,
+        true,
+        QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
+    );
+
+    JoinTestHelper.verifyCursors(
+        new HashJoinSegmentStorageAdapter(
+            factSegment.asStorageAdapter(),
+            joinableClauses,
+            preAnalysis
+        ).makeCursors(
+            originalFilter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of()
+    );
+  }
 }
diff --git 
a/processing/src/test/java/org/apache/druid/segment/join/JoinFilterAnalyzerTest.java
 
b/processing/src/test/java/org/apache/druid/segment/join/JoinFilterAnalyzerTest.java
index ae97b7e..5c8d903 100644
--- 
a/processing/src/test/java/org/apache/druid/segment/join/JoinFilterAnalyzerTest.java
+++ 
b/processing/src/test/java/org/apache/druid/segment/join/JoinFilterAnalyzerTest.java
@@ -41,6 +41,7 @@ import org.apache.druid.segment.filter.SelectorFilter;
 import org.apache.druid.segment.join.filter.JoinFilterAnalyzer;
 import org.apache.druid.segment.join.filter.JoinFilterPreAnalysis;
 import org.apache.druid.segment.join.filter.JoinFilterSplit;
+import org.apache.druid.segment.join.lookup.LookupJoinable;
 import org.apache.druid.segment.join.table.IndexedTableJoinable;
 import org.apache.druid.segment.virtual.ExpressionVirtualColumn;
 import org.junit.Assert;
@@ -1002,6 +1003,95 @@ public class JoinFilterAnalyzerTest extends 
BaseHashJoinSegmentStorageAdapterTes
   }
 
   @Test
+  public void 
test_filterPushDown_factConcatExpressionToCountryLeftFilterOnChannelAndCountryNameUsingLookup()
+  {
+    JoinableClause factExprToCountry = new JoinableClause(
+        FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+        LookupJoinable.wrap(countryIsoCodeToNameLookup),
+        JoinType.LEFT,
+        JoinConditionAnalysis.forExpression(
+            StringUtils.format(
+                "\"%sk\" == concat(countryIsoCode, regionIsoCode)",
+                FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX
+            ),
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX,
+            ExprMacroTable.nil()
+        )
+    );
+    List<JoinableClause> joinableClauses = ImmutableList.of(
+        factExprToCountry
+    );
+    Filter filter = new AndFilter(
+        ImmutableList.of(
+            new SelectorFilter("channel", "#en.wikipedia"),
+            new SelectorFilter("c1.v", "Usca")
+        )
+    );
+
+    JoinFilterPreAnalysis joinFilterPreAnalysis = simplePreAnalysis(
+        joinableClauses,
+        filter
+    );
+
+    HashJoinSegmentStorageAdapter adapter = new HashJoinSegmentStorageAdapter(
+        factSegment.asStorageAdapter(),
+        joinableClauses,
+        joinFilterPreAnalysis
+    );
+
+    ExpressionVirtualColumn expectedVirtualColumn = new 
ExpressionVirtualColumn(
+        "JOIN-FILTER-PUSHDOWN-VIRTUAL-COLUMN-0",
+        "concat(countryIsoCode, regionIsoCode)",
+        ValueType.STRING,
+        ExprMacroTable.nil()
+    );
+    JoinFilterSplit expectedFilterSplit = new JoinFilterSplit(
+        new AndFilter(
+            ImmutableList.of(
+                new SelectorFilter("channel", "#en.wikipedia"),
+                new InDimFilter("JOIN-FILTER-PUSHDOWN-VIRTUAL-COLUMN-0", 
ImmutableSet.of("USCA"), null, null).toFilter()
+            )
+        ),
+        new SelectorFilter("c1.v", "Usca"),
+        ImmutableList.of(
+            expectedVirtualColumn
+        )
+    );
+    JoinFilterSplit actualFilterSplit = 
JoinFilterAnalyzer.splitFilter(joinFilterPreAnalysis);
+    Assert.assertEquals(
+        expectedFilterSplit.getBaseTableFilter(),
+        actualFilterSplit.getBaseTableFilter()
+    );
+    Assert.assertEquals(
+        expectedFilterSplit.getJoinTableFilter(),
+        actualFilterSplit.getJoinTableFilter()
+    );
+    ExpressionVirtualColumn actualVirtualColumn = (ExpressionVirtualColumn) 
actualFilterSplit.getPushDownVirtualColumns()
+                                                                               
              .get(0);
+    compareExpressionVirtualColumns(expectedVirtualColumn, 
actualVirtualColumn);
+
+    JoinTestHelper.verifyCursors(
+        adapter.makeCursors(
+            filter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of(
+            new Object[]{"President of India", "Usca"},
+            new Object[]{"Otjiwarongo Airport", "Usca"},
+            new Object[]{"Carlo Curti", "Usca"}
+        )
+    );
+  }
+
+  @Test
   public void 
test_filterPushDown_factToCountryRightWithFilterOnChannelAndJoinable()
   {
     List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryOnIsoCode(JoinType.RIGHT));
@@ -1058,6 +1148,61 @@ public class JoinFilterAnalyzerTest extends 
BaseHashJoinSegmentStorageAdapterTes
   }
 
   @Test
+  public void 
test_filterPushDown_factToCountryRightWithFilterOnChannelAndJoinableUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingIsoCodeLookup(JoinType.RIGHT));
+    Filter originalFilter = new AndFilter(
+        ImmutableList.of(
+            new SelectorFilter("channel", "#de.wikipedia"),
+            new SelectorFilter(FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v", 
"Germany")
+        )
+    );
+    JoinFilterPreAnalysis joinFilterPreAnalysis = simplePreAnalysis(
+        joinableClauses,
+        originalFilter
+    );
+    HashJoinSegmentStorageAdapter adapter = new HashJoinSegmentStorageAdapter(
+        factSegment.asStorageAdapter(),
+        joinableClauses,
+        joinFilterPreAnalysis
+    );
+
+    JoinFilterSplit expectedFilterSplit = new JoinFilterSplit(
+        new AndFilter(
+            ImmutableList.of(
+                new SelectorFilter("channel", "#de.wikipedia"),
+                new InDimFilter("countryIsoCode", ImmutableSet.of("DE"), null, 
null).toFilter()
+            )
+        ),
+        new SelectorFilter(FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v", 
"Germany"),
+        ImmutableList.of()
+    );
+    JoinFilterSplit actualFilterSplit = 
JoinFilterAnalyzer.splitFilter(joinFilterPreAnalysis);
+    Assert.assertEquals(expectedFilterSplit, actualFilterSplit);
+
+    JoinTestHelper.verifyCursors(
+        adapter.makeCursors(
+            originalFilter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            "countryNumber",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of(
+            new Object[]{"Diskussion:Sebastian Schulz", "DE", 3L, "DE", 
"Germany"}
+        )
+    );
+  }
+
+  @Test
   public void test_filterPushDown_factToCountryRightWithFilterOnNullColumns()
   {
     List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryOnIsoCode(JoinType.RIGHT));
@@ -1112,6 +1257,59 @@ public class JoinFilterAnalyzerTest extends 
BaseHashJoinSegmentStorageAdapterTes
   }
 
   @Test
+  public void 
test_filterPushDown_factToCountryRightWithFilterOnNullColumnsUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingIsoCodeLookup(JoinType.RIGHT));
+    Filter originalFilter = new AndFilter(
+        ImmutableList.of(
+            new SelectorFilter("channel", null),
+            new SelectorFilter(FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v", null)
+        )
+    );
+    JoinFilterPreAnalysis joinFilterPreAnalysis = simplePreAnalysis(
+        joinableClauses,
+        originalFilter
+    );
+    HashJoinSegmentStorageAdapter adapter = new HashJoinSegmentStorageAdapter(
+        factSegment.asStorageAdapter(),
+        joinableClauses,
+        joinFilterPreAnalysis
+    );
+
+    JoinFilterSplit expectedFilterSplit = new JoinFilterSplit(
+        null,
+        new AndFilter(
+            ImmutableList.of(
+                new SelectorFilter("channel", null),
+                new SelectorFilter(FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v", 
null)
+            )
+        ),
+        ImmutableList.of()
+    );
+    JoinFilterSplit actualFilterSplit = 
JoinFilterAnalyzer.splitFilter(joinFilterPreAnalysis);
+    Assert.assertEquals(expectedFilterSplit, actualFilterSplit);
+
+    JoinTestHelper.verifyCursors(
+        adapter.makeCursors(
+            originalFilter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            "countryNumber",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of()
+    );
+  }
+
+  @Test
   public void 
test_filterPushDown_factToCountryInnerUsingCountryNumberFilterOnChannelAndCountryName()
   {
     List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryOnNumber(JoinType.INNER));
@@ -1174,6 +1372,67 @@ public class JoinFilterAnalyzerTest extends 
BaseHashJoinSegmentStorageAdapterTes
   }
 
   @Test
+  public void 
test_filterPushDown_factToCountryInnerUsingCountryNumberFilterOnChannelAndCountryNameUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingNumberLookup(JoinType.INNER));
+    Filter originalFilter = new AndFilter(
+        ImmutableList.of(
+            new SelectorFilter("channel", "#en.wikipedia"),
+            new SelectorFilter(FACT_TO_COUNTRY_ON_NUMBER_PREFIX + "v", 
"Australia")
+        )
+    );
+    JoinFilterPreAnalysis joinFilterPreAnalysis = simplePreAnalysis(
+        joinableClauses,
+        originalFilter
+    );
+    HashJoinSegmentStorageAdapter adapter = new HashJoinSegmentStorageAdapter(
+        factSegment.asStorageAdapter(),
+        joinableClauses,
+        joinFilterPreAnalysis
+    );
+
+    JoinFilterSplit expectedFilterSplit = new JoinFilterSplit(
+        new AndFilter(
+            ImmutableList.of(
+                new SelectorFilter("channel", "#en.wikipedia"),
+                new InDimFilter("countryNumber", ImmutableSet.of("0"), null, 
null).toFilter()
+            )
+        ),
+        new SelectorFilter(FACT_TO_COUNTRY_ON_NUMBER_PREFIX + "v", 
"Australia"),
+        ImmutableList.of()
+    );
+    JoinFilterSplit actualFilterSplit = 
JoinFilterAnalyzer.splitFilter(joinFilterPreAnalysis);
+    Assert.assertEquals(expectedFilterSplit, actualFilterSplit);
+
+    // In non-SQL-compatible mode, we get an extra row, since the 'null' 
countryNumber for "Talk:Oswald Tilghman"
+    // is interpreted as 0 (a.k.a. Australia).
+    JoinTestHelper.verifyCursors(
+        adapter.makeCursors(
+            originalFilter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            FACT_TO_COUNTRY_ON_NUMBER_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_NUMBER_PREFIX + "v"
+        ),
+        NullHandling.sqlCompatible() ?
+        ImmutableList.of(
+            new Object[]{"Peremptory norm", "AU", "0", "Australia"}
+        ) :
+        ImmutableList.of(
+            new Object[]{"Talk:Oswald Tilghman", null, "0", "Australia"},
+            new Object[]{"Peremptory norm", "AU", "0", "Australia"}
+        )
+    );
+  }
+
+  @Test
   public void 
test_filterPushDown_factToCountryInnerUsingCountryNumberFilterOnNulls()
   {
     List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryOnNumber(JoinType.INNER));
@@ -1227,6 +1486,58 @@ public class JoinFilterAnalyzerTest extends 
BaseHashJoinSegmentStorageAdapterTes
   }
 
   @Test
+  public void 
test_filterPushDown_factToCountryInnerUsingCountryNumberFilterOnNullsUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingIsoCodeLookup(JoinType.INNER));
+    Filter originalFilter = new AndFilter(
+        ImmutableList.of(
+            new SelectorFilter("channel", null),
+            new SelectorFilter(FACT_TO_COUNTRY_ON_NUMBER_PREFIX + "v", null)
+        )
+    );
+    JoinFilterPreAnalysis joinFilterPreAnalysis = simplePreAnalysis(
+        joinableClauses,
+        originalFilter
+    );
+    HashJoinSegmentStorageAdapter adapter = new HashJoinSegmentStorageAdapter(
+        factSegment.asStorageAdapter(),
+        joinableClauses,
+        joinFilterPreAnalysis
+    );
+
+    JoinFilterSplit expectedFilterSplit = new JoinFilterSplit(
+        null,
+        new AndFilter(
+            ImmutableList.of(
+                new SelectorFilter("channel", null),
+                new SelectorFilter(FACT_TO_COUNTRY_ON_NUMBER_PREFIX + "v", 
null)
+            )
+        ),
+        ImmutableList.of()
+    );
+    JoinFilterSplit actualFilterSplit = 
JoinFilterAnalyzer.splitFilter(joinFilterPreAnalysis);
+    Assert.assertEquals(expectedFilterSplit, actualFilterSplit);
+
+    JoinTestHelper.verifyCursors(
+        adapter.makeCursors(
+            originalFilter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            FACT_TO_COUNTRY_ON_NUMBER_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_NUMBER_PREFIX + "v"
+        ),
+        ImmutableList.of()
+    );
+  }
+
+  @Test
   public void 
test_filterPushDown_factToCountryFullWithFilterOnChannelAndCountryName()
   {
     Filter filter = new AndFilter(
@@ -1283,6 +1594,61 @@ public class JoinFilterAnalyzerTest extends 
BaseHashJoinSegmentStorageAdapterTes
   }
 
   @Test
+  public void 
test_filterPushDown_factToCountryFullWithFilterOnChannelAndCountryNameUsingLookup()
+  {
+    Filter filter = new AndFilter(
+        ImmutableList.of(
+            new SelectorFilter("channel", "#es.wikipedia"),
+            new SelectorFilter(FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v", "El 
Salvador")
+        )
+    );
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingIsoCodeLookup(JoinType.FULL));
+    JoinFilterPreAnalysis joinFilterPreAnalysis = simplePreAnalysis(
+        joinableClauses,
+        filter
+    );
+    HashJoinSegmentStorageAdapter adapter = new HashJoinSegmentStorageAdapter(
+        factSegment.asStorageAdapter(),
+        joinableClauses,
+        joinFilterPreAnalysis
+    );
+
+    JoinFilterSplit expectedFilterSplit = new JoinFilterSplit(
+        new AndFilter(
+            ImmutableList.of(
+                new SelectorFilter("channel", "#es.wikipedia"),
+                new InDimFilter("countryIsoCode", ImmutableSet.of("SV"), null, 
null).toFilter()
+            )
+        ),
+        new SelectorFilter(FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v", "El 
Salvador"),
+        ImmutableList.of()
+    );
+    JoinFilterSplit actualFilterSplit = 
JoinFilterAnalyzer.splitFilter(joinFilterPreAnalysis);
+    Assert.assertEquals(expectedFilterSplit, actualFilterSplit);
+
+    JoinTestHelper.verifyCursors(
+        adapter.makeCursors(
+            filter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            "countryNumber",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of(
+            new Object[]{"Wendigo", "SV", 12L, "SV", "El Salvador"}
+        )
+    );
+  }
+
+  @Test
   public void test_filterPushDown_factToCountryFullWithFilterOnNulls()
   {
     List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryOnIsoCode(JoinType.FULL));
@@ -1337,6 +1703,59 @@ public class JoinFilterAnalyzerTest extends 
BaseHashJoinSegmentStorageAdapterTes
   }
 
   @Test
+  public void 
test_filterPushDown_factToCountryFullWithFilterOnNullsUsingLookup()
+  {
+    List<JoinableClause> joinableClauses = 
ImmutableList.of(factToCountryNameUsingIsoCodeLookup(JoinType.FULL));
+    Filter originalFilter = new AndFilter(
+        ImmutableList.of(
+            new SelectorFilter("channel", null),
+            new SelectorFilter(FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v", null)
+        )
+    );
+    JoinFilterPreAnalysis joinFilterPreAnalysis = simplePreAnalysis(
+        joinableClauses,
+        originalFilter
+    );
+    HashJoinSegmentStorageAdapter adapter = new HashJoinSegmentStorageAdapter(
+        factSegment.asStorageAdapter(),
+        joinableClauses,
+        joinFilterPreAnalysis
+    );
+
+    JoinFilterSplit expectedFilterSplit = new JoinFilterSplit(
+        null,
+        new AndFilter(
+            ImmutableList.of(
+                new SelectorFilter("channel", null),
+                new SelectorFilter(FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v", 
null)
+            )
+        ),
+        ImmutableList.of()
+    );
+    JoinFilterSplit actualFilterSplit = 
JoinFilterAnalyzer.splitFilter(joinFilterPreAnalysis);
+    Assert.assertEquals(expectedFilterSplit, actualFilterSplit);
+
+    JoinTestHelper.verifyCursors(
+        adapter.makeCursors(
+            originalFilter,
+            Intervals.ETERNITY,
+            VirtualColumns.EMPTY,
+            Granularities.ALL,
+            false,
+            null
+        ),
+        ImmutableList.of(
+            "page",
+            "countryIsoCode",
+            "countryNumber",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "k",
+            FACT_TO_COUNTRY_ON_ISO_CODE_PREFIX + "v"
+        ),
+        ImmutableList.of()
+    );
+  }
+
+  @Test
   public void 
test_filterPushDown_factToRegionTwoColumnsToOneRHSColumnAndFilterOnRHS()
   {
     JoinableClause factExprToRegon = new JoinableClause(
diff --git 
a/processing/src/test/java/org/apache/druid/segment/join/table/LookupJoinMatcherTest.java
 
b/processing/src/test/java/org/apache/druid/segment/join/table/LookupJoinMatcherTest.java
new file mode 100644
index 0000000..f5f7f3f
--- /dev/null
+++ 
b/processing/src/test/java/org/apache/druid/segment/join/table/LookupJoinMatcherTest.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.segment.join.table;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.math.expr.ExprMacroTable;
+import org.apache.druid.query.dimension.DefaultDimensionSpec;
+import org.apache.druid.query.dimension.DimensionSpec;
+import org.apache.druid.query.lookup.LookupExtractor;
+import org.apache.druid.segment.ColumnSelectorFactory;
+import org.apache.druid.segment.DimensionSelector;
+import org.apache.druid.segment.data.SingleIndexedInt;
+import org.apache.druid.segment.join.JoinConditionAnalysis;
+import org.apache.druid.segment.join.lookup.LookupJoinMatcher;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Map;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LookupJoinMatcherTest
+{
+  private final Map<String, String> lookupMap =
+      ImmutableMap.of("foo", "bar", "null", "", "empty String", "", "", 
"empty_string");
+  private static final String PREFIX = "j.";
+
+  @Mock
+  private LookupExtractor extractor;
+
+  @Mock
+  private ColumnSelectorFactory leftSelectorFactory;
+
+  @Mock
+  private DimensionSelector dimensionSelector;
+
+  private LookupJoinMatcher target;
+
+  @Before
+  public void setUp()
+  {
+    Mockito.doReturn(true).when(extractor).canIterate();
+    Mockito.doReturn(lookupMap.entrySet()).when(extractor).iterable();
+  }
+
+  @Test
+  public void 
testCreateConditionAlwaysFalseShouldReturnSuccessfullyAndNotThrowException()
+  {
+    JoinConditionAnalysis condition = JoinConditionAnalysis.forExpression("0", 
PREFIX, ExprMacroTable.nil());
+    target = LookupJoinMatcher.create(extractor, leftSelectorFactory, 
condition, false);
+    Assert.assertNotNull(target);
+    target = LookupJoinMatcher.create(extractor, leftSelectorFactory, 
condition, true);
+    Assert.assertNotNull(target);
+  }
+
+  @Test
+  public void 
testCreateConditionAlwaysTrueShouldReturnSuccessfullyAndNotThrowException()
+  {
+    JoinConditionAnalysis condition = JoinConditionAnalysis.forExpression("1", 
PREFIX, ExprMacroTable.nil());
+    Mockito.doReturn(true).when(extractor).canIterate();
+    target = LookupJoinMatcher.create(extractor, leftSelectorFactory, 
condition, false);
+    Assert.assertNotNull(target);
+    target = LookupJoinMatcher.create(extractor, leftSelectorFactory, 
condition, true);
+    Assert.assertNotNull(target);
+  }
+
+  @Test
+  public void testMatchConditionAlwaysTrue()
+  {
+    JoinConditionAnalysis condition = JoinConditionAnalysis.forExpression("1", 
PREFIX, ExprMacroTable.nil());
+    target = LookupJoinMatcher.create(extractor, leftSelectorFactory, 
condition, true);
+    // Test match first
+    target.matchCondition();
+    Assert.assertTrue(target.hasMatch());
+    verifyMatch("foo", "bar");
+    // Test match second
+    target.nextMatch();
+    Assert.assertTrue(target.hasMatch());
+    verifyMatch("null", "");
+    // Test match third
+    target.nextMatch();
+    Assert.assertTrue(target.hasMatch());
+    verifyMatch("empty String", "");
+    // Test match forth
+    target.nextMatch();
+    Assert.assertTrue(target.hasMatch());
+    verifyMatch("", "empty_string");
+    // Test no more
+    target.nextMatch();
+    Assert.assertFalse(target.hasMatch());
+  }
+
+  @Test
+  public void testMatchConditionAlwaysFalse()
+  {
+    JoinConditionAnalysis condition = JoinConditionAnalysis.forExpression("0", 
PREFIX, ExprMacroTable.nil());
+    target = LookupJoinMatcher.create(extractor, leftSelectorFactory, 
condition, true);
+    // Test match first
+    target.matchCondition();
+    Assert.assertFalse(target.hasMatch());
+    verifyMatch(null, null);
+  }
+
+  @Test
+  public void testMatchConditionSometimesTrueSometimesFalse()
+  {
+    final int index = 1;
+    SingleIndexedInt row = new SingleIndexedInt();
+    row.setValue(index);
+    
Mockito.doReturn(dimensionSelector).when(leftSelectorFactory).makeDimensionSelector(ArgumentMatchers.any(DimensionSpec.class));
+    Mockito.doReturn(row).when(dimensionSelector).getRow();
+    Mockito.doReturn("foo").when(dimensionSelector).lookupName(index);
+    Mockito.doReturn("bar").when(extractor).apply("foo");
+
+    JoinConditionAnalysis condition = JoinConditionAnalysis.forExpression(
+        StringUtils.format("\"%sk\" == foo", PREFIX),
+        PREFIX,
+        ExprMacroTable.nil()
+    );
+    target = LookupJoinMatcher.create(extractor, leftSelectorFactory, 
condition, true);
+    // Test match
+    target.matchCondition();
+    Assert.assertTrue(target.hasMatch());
+    verifyMatch("foo", "bar");
+    // Test no more
+    target.nextMatch();
+    Assert.assertFalse(target.hasMatch());
+  }
+
+  private void verifyMatch(String expectedKey, String expectedValue)
+  {
+    DimensionSelector selector = target.getColumnSelectorFactory()
+                                             
.makeDimensionSelector(DefaultDimensionSpec.of("k"));
+    Assert.assertEquals(-1, selector.getValueCardinality());
+    Assert.assertEquals(expectedKey, selector.lookupName(0));
+    Assert.assertEquals(expectedKey, selector.lookupName(0));
+    Assert.assertNull(selector.idLookup());
+
+    selector = target.getColumnSelectorFactory()
+                                             
.makeDimensionSelector(DefaultDimensionSpec.of("v"));
+    Assert.assertEquals(-1, selector.getValueCardinality());
+    Assert.assertEquals(expectedValue, selector.lookupName(0));
+    Assert.assertEquals(expectedValue, selector.lookupName(0));
+    Assert.assertNull(selector.idLookup());
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@druid.apache.org
For additional commands, e-mail: commits-h...@druid.apache.org

Reply via email to