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