Add getRightEquiConditionKeys to JoinConditionAnalysis (#9287)

* Add getRightColumns to JoinConditionAnalysis

This change other implementations of JoinableFactory to ask the analysis
for the right key columns instead of having to calculate it themselves.

* Address some review comments

* more code review stuff
This commit is contained in:
Suneet Saldanha 2020-01-29 22:31:29 -08:00 committed by GitHub
parent a1494c30e0
commit 6b44d4aa80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 43 additions and 16 deletions

View File

@ -32,6 +32,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/** /**
* Represents analysis of a join condition. * Represents analysis of a join condition.
@ -55,6 +57,7 @@ public class JoinConditionAnalysis
private final boolean isAlwaysFalse; private final boolean isAlwaysFalse;
private final boolean isAlwaysTrue; private final boolean isAlwaysTrue;
private final boolean canHashJoin; private final boolean canHashJoin;
private final Set<String> rightKeyColumns;
private JoinConditionAnalysis( private JoinConditionAnalysis(
final String originalExpression, final String originalExpression,
@ -76,6 +79,7 @@ public class JoinConditionAnalysis
.allMatch(expr -> expr.isLiteral() && expr.eval( .allMatch(expr -> expr.isLiteral() && expr.eval(
ExprUtils.nilBindings()).asBoolean()); ExprUtils.nilBindings()).asBoolean());
canHashJoin = nonEquiConditions.stream().allMatch(Expr::isLiteral); canHashJoin = nonEquiConditions.stream().allMatch(Expr::isLiteral);
rightKeyColumns = getEquiConditions().stream().map(Equality::getRightColumn).distinct().collect(Collectors.toSet());
} }
/** /**
@ -176,6 +180,14 @@ public class JoinConditionAnalysis
return canHashJoin; return canHashJoin;
} }
/**
* Returns the distinct column keys from the RHS required to evaluate the equi conditions.
*/
public Set<String> getRightEquiConditionKeys()
{
return rightKeyColumns;
}
@Override @Override
public boolean equals(Object o) public boolean equals(Object o)
{ {

View File

@ -191,9 +191,9 @@ public class LookupJoinMatcher implements JoinMatcher
keyExprs = null; keyExprs = null;
} else if (!condition.getNonEquiConditions().isEmpty()) { } else if (!condition.getNonEquiConditions().isEmpty()) {
throw new IAE("Cannot join lookup with non-equi condition: %s", condition); throw new IAE("Cannot join lookup with non-equi condition: %s", condition);
} else if (!condition.getEquiConditions() } else if (!condition.getRightEquiConditionKeys()
.stream() .stream()
.allMatch(eq -> eq.getRightColumn().equals(LookupColumnSelectorFactory.KEY_COLUMN))) { .allMatch(LookupColumnSelectorFactory.KEY_COLUMN::equals)) {
throw new IAE("Cannot join lookup with condition referring to non-key column: %s", condition); throw new IAE("Cannot join lookup with condition referring to non-key column: %s", condition);
} else { } else {
keyExprs = condition.getEquiConditions().stream().map(Equality::getLeftExpr).collect(Collectors.toList()); keyExprs = condition.getEquiConditions().stream().map(Equality::getLeftExpr).collect(Collectors.toList());

View File

@ -25,6 +25,7 @@ import org.apache.druid.segment.column.ValueType;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* An interface to a table where some columns (the 'key columns') have indexes that enable fast lookups. * An interface to a table where some columns (the 'key columns') have indexes that enable fast lookups.
@ -36,7 +37,7 @@ public interface IndexedTable
/** /**
* Returns the columns of this table that have indexes. * Returns the columns of this table that have indexes.
*/ */
List<String> keyColumns(); Set<String> keyColumns();
/** /**
* Returns all columns of this table, including the key and non-key columns. * Returns all columns of this table, including the key and non-key columns.

View File

@ -33,6 +33,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -48,13 +49,13 @@ public class RowBasedIndexedTable<RowType> implements IndexedTable
private final List<String> columns; private final List<String> columns;
private final List<ValueType> columnTypes; private final List<ValueType> columnTypes;
private final List<Function<RowType, Object>> columnFunctions; private final List<Function<RowType, Object>> columnFunctions;
private final List<String> keyColumns; private final Set<String> keyColumns;
public RowBasedIndexedTable( public RowBasedIndexedTable(
final List<RowType> table, final List<RowType> table,
final RowAdapter<RowType> rowAdapter, final RowAdapter<RowType> rowAdapter,
final Map<String, ValueType> rowSignature, final Map<String, ValueType> rowSignature,
final List<String> keyColumns final Set<String> keyColumns
) )
{ {
this.table = table; this.table = table;
@ -107,7 +108,7 @@ public class RowBasedIndexedTable<RowType> implements IndexedTable
} }
@Override @Override
public List<String> keyColumns() public Set<String> keyColumns()
{ {
return keyColumns; return keyColumns;
} }

View File

@ -20,6 +20,7 @@
package org.apache.druid.segment.join; package org.apache.druid.segment.join;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.equalsverifier.EqualsVerifier;
import org.apache.druid.common.config.NullHandling; import org.apache.druid.common.config.NullHandling;
import org.apache.druid.java.util.common.Pair; import org.apache.druid.java.util.common.Pair;
@ -60,6 +61,7 @@ public class JoinConditionAnalysisTest
ImmutableList.of(), ImmutableList.of(),
exprsToStrings(analysis.getNonEquiConditions()) exprsToStrings(analysis.getNonEquiConditions())
); );
Assert.assertEquals(analysis.getRightEquiConditionKeys(), ImmutableSet.of("y"));
} }
@Test @Test
@ -80,6 +82,7 @@ public class JoinConditionAnalysisTest
ImmutableList.of(), ImmutableList.of(),
exprsToStrings(analysis.getNonEquiConditions()) exprsToStrings(analysis.getNonEquiConditions())
); );
Assert.assertEquals(analysis.getRightEquiConditionKeys(), ImmutableSet.of("y"));
} }
@Test @Test
@ -100,6 +103,7 @@ public class JoinConditionAnalysisTest
ImmutableList.of(), ImmutableList.of(),
exprsToStrings(analysis.getNonEquiConditions()) exprsToStrings(analysis.getNonEquiConditions())
); );
Assert.assertEquals(analysis.getRightEquiConditionKeys(), ImmutableSet.of("z"));
} }
@Test @Test
@ -120,6 +124,7 @@ public class JoinConditionAnalysisTest
ImmutableList.of("(== (+ j.x j.y) z)"), ImmutableList.of("(== (+ j.x j.y) z)"),
exprsToStrings(analysis.getNonEquiConditions()) exprsToStrings(analysis.getNonEquiConditions())
); );
Assert.assertTrue(analysis.getRightEquiConditionKeys().isEmpty());
} }
@Test @Test
@ -140,6 +145,7 @@ public class JoinConditionAnalysisTest
ImmutableList.of("(== (+ x j.y) j.z)"), ImmutableList.of("(== (+ x j.y) j.z)"),
exprsToStrings(analysis.getNonEquiConditions()) exprsToStrings(analysis.getNonEquiConditions())
); );
Assert.assertTrue(analysis.getRightEquiConditionKeys().isEmpty());
} }
@Test @Test
@ -160,6 +166,7 @@ public class JoinConditionAnalysisTest
ImmutableList.of("2"), ImmutableList.of("2"),
exprsToStrings(analysis.getNonEquiConditions()) exprsToStrings(analysis.getNonEquiConditions())
); );
Assert.assertTrue(analysis.getRightEquiConditionKeys().isEmpty());
} }
@Test @Test
@ -180,6 +187,7 @@ public class JoinConditionAnalysisTest
ImmutableList.of("0"), ImmutableList.of("0"),
exprsToStrings(analysis.getNonEquiConditions()) exprsToStrings(analysis.getNonEquiConditions())
); );
Assert.assertTrue(analysis.getRightEquiConditionKeys().isEmpty());
} }
@Test @Test
@ -200,6 +208,7 @@ public class JoinConditionAnalysisTest
ImmutableList.of("(== x 1)"), ImmutableList.of("(== x 1)"),
exprsToStrings(analysis.getNonEquiConditions()) exprsToStrings(analysis.getNonEquiConditions())
); );
Assert.assertTrue(analysis.getRightEquiConditionKeys().isEmpty());
} }
@Test @Test
@ -220,6 +229,7 @@ public class JoinConditionAnalysisTest
ImmutableList.of(), ImmutableList.of(),
exprsToStrings(analysis.getNonEquiConditions()) exprsToStrings(analysis.getNonEquiConditions())
); );
Assert.assertEquals(analysis.getRightEquiConditionKeys(), ImmutableSet.of("x"));
} }
@Test @Test
@ -240,6 +250,7 @@ public class JoinConditionAnalysisTest
ImmutableList.of(), ImmutableList.of(),
exprsToStrings(analysis.getNonEquiConditions()) exprsToStrings(analysis.getNonEquiConditions())
); );
Assert.assertEquals(analysis.getRightEquiConditionKeys(), ImmutableSet.of("y", "z", "zz"));
} }
@Test @Test
@ -260,6 +271,7 @@ public class JoinConditionAnalysisTest
ImmutableList.of("(|| (== (+ x y) j.z) (== z j.zz))"), ImmutableList.of("(|| (== (+ x y) j.z) (== z j.zz))"),
exprsToStrings(analysis.getNonEquiConditions()) exprsToStrings(analysis.getNonEquiConditions())
); );
Assert.assertEquals(analysis.getRightEquiConditionKeys(), ImmutableSet.of("y"));
} }
@Test @Test
@ -270,8 +282,8 @@ public class JoinConditionAnalysisTest
.withIgnoredFields( .withIgnoredFields(
// These fields are tightly coupled with originalExpression // These fields are tightly coupled with originalExpression
"equiConditions", "nonEquiConditions", "equiConditions", "nonEquiConditions",
// These fields are calculated from nonEquiConditions // These fields are calculated from other other fields in the class
"isAlwaysTrue", "isAlwaysFalse", "canHashJoin") "isAlwaysTrue", "isAlwaysFalse", "canHashJoin", "rightKeyColumns")
.verify(); .verify();
} }

View File

@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.apache.druid.common.config.NullHandling; import org.apache.druid.common.config.NullHandling;
import org.apache.druid.data.input.InputRow; import org.apache.druid.data.input.InputRow;
@ -252,7 +253,7 @@ public class JoinTestHelper
rows, rows,
createMapRowAdapter(COUNTRIES_SIGNATURE), createMapRowAdapter(COUNTRIES_SIGNATURE),
COUNTRIES_SIGNATURE, COUNTRIES_SIGNATURE,
ImmutableList.of("countryNumber", "countryIsoCode") ImmutableSet.of("countryNumber", "countryIsoCode")
) )
); );
} }
@ -265,7 +266,7 @@ public class JoinTestHelper
rows, rows,
createMapRowAdapter(REGIONS_SIGNATURE), createMapRowAdapter(REGIONS_SIGNATURE),
REGIONS_SIGNATURE, REGIONS_SIGNATURE,
ImmutableList.of("regionIsoCode", "countryIsoCode") ImmutableSet.of("regionIsoCode", "countryIsoCode")
) )
); );
} }

View File

@ -20,6 +20,7 @@
package org.apache.druid.segment.join.table; package org.apache.druid.segment.join.table;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.apache.druid.math.expr.ExprMacroTable; import org.apache.druid.math.expr.ExprMacroTable;
import org.apache.druid.query.InlineDataSource; import org.apache.druid.query.InlineDataSource;
import org.apache.druid.query.dimension.DefaultDimensionSpec; import org.apache.druid.query.dimension.DefaultDimensionSpec;
@ -72,7 +73,7 @@ public class IndexedTableJoinableTest
inlineDataSource.getRowsAsList(), inlineDataSource.getRowsAsList(),
inlineDataSource.rowAdapter(), inlineDataSource.rowAdapter(),
inlineDataSource.getRowSignature(), inlineDataSource.getRowSignature(),
ImmutableList.of("str") ImmutableSet.of("str")
); );
@Test @Test

View File

@ -21,6 +21,7 @@ package org.apache.druid.segment.join.table;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.druid.common.config.NullHandling; import org.apache.druid.common.config.NullHandling;
import org.apache.druid.segment.column.ValueType; import org.apache.druid.segment.column.ValueType;
import org.apache.druid.segment.join.JoinTestHelper; import org.apache.druid.segment.join.JoinTestHelper;
@ -64,7 +65,7 @@ public class RowBasedIndexedTableTest
@Test @Test
public void test_keyColumns_countries() public void test_keyColumns_countries()
{ {
Assert.assertEquals(ImmutableList.of("countryNumber", "countryIsoCode"), countriesTable.keyColumns()); Assert.assertEquals(ImmutableSet.of("countryNumber", "countryIsoCode"), countriesTable.keyColumns());
} }
@Test @Test

View File

@ -25,9 +25,8 @@ import org.apache.druid.segment.join.table.IndexedTable;
import org.apache.druid.segment.join.table.IndexedTableJoinable; import org.apache.druid.segment.join.table.IndexedTableJoinable;
import org.apache.druid.segment.join.table.RowBasedIndexedTable; import org.apache.druid.segment.join.table.RowBasedIndexedTable;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.Set;
/** /**
* A {@link JoinableFactory} for {@link InlineDataSource}. It works by building an {@link IndexedTable}. * A {@link JoinableFactory} for {@link InlineDataSource}. It works by building an {@link IndexedTable}.
@ -39,8 +38,7 @@ public class InlineJoinableFactory implements JoinableFactory
{ {
if (condition.canHashJoin() && dataSource instanceof InlineDataSource) { if (condition.canHashJoin() && dataSource instanceof InlineDataSource) {
final InlineDataSource inlineDataSource = (InlineDataSource) dataSource; final InlineDataSource inlineDataSource = (InlineDataSource) dataSource;
final List<String> rightKeyColumns = final Set<String> rightKeyColumns = condition.getRightEquiConditionKeys();
condition.getEquiConditions().stream().map(Equality::getRightColumn).distinct().collect(Collectors.toList());
return Optional.of( return Optional.of(
new IndexedTableJoinable( new IndexedTableJoinable(