Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CALCITE-6640] RelMdUniqueKeys grows exponentially when key columns are repeated in projections #4013

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,14 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand All @@ -63,8 +62,6 @@
import static org.apache.calcite.rel.metadata.RelMdColumnUniqueness.PASSTHROUGH_AGGREGATIONS;
import static org.apache.calcite.rel.metadata.RelMdColumnUniqueness.getConstantColumnSet;

import static java.util.Objects.requireNonNull;

/**
* RelMdUniqueKeys supplies a default implementation of
* {@link RelMetadataQuery#getUniqueKeys} for the standard logical algebra.
Expand Down Expand Up @@ -172,8 +169,7 @@ private static Set<ImmutableBitSet> getProjectUniqueKeys(SingleRel rel, RelMetad
return ImmutableSet.of();
}

Map<Integer, ImmutableBitSet> mapInToOutPos =
Maps.transformValues(inToOutPosBuilder.build().asMap(), ImmutableBitSet::of);
Multimap<Integer, Integer> mapInToOutPos = inToOutPosBuilder.build();

ImmutableSet.Builder<ImmutableBitSet> resultBuilder = ImmutableSet.builder();
// Now add to the projUniqueKeySet the child keys that are fully
Expand All @@ -184,19 +180,12 @@ private static Set<ImmutableBitSet> getProjectUniqueKeys(SingleRel rel, RelMetad
continue;
}
// colMask is mapped to output project, however, the column can be mapped more than once:
// select id, id, id, unique2, unique2
// the resulting unique keys would be {{0},{3}}, {{0},{4}}, {{0},{1},{4}}, ...

Iterable<List<ImmutableBitSet>> product =
Linq4j.product(
Util.transform(colMask, in ->
Util.filter(
requireNonNull(mapInToOutPos.get(in),
() -> "no entry for column " + in
+ " in mapInToOutPos: " + mapInToOutPos).powerSet(),
bs -> !bs.isEmpty())));

resultBuilder.addAll(Util.transform(product, ImmutableBitSet::union));
// select key1, key1, val1, val2, key2 from ...
// the resulting unique keys would be {{0},{4}}, {{1},{4}}
zabetak marked this conversation as resolved.
Show resolved Hide resolved

Iterable<List<Integer>> product = Linq4j.product(Util.transform(colMask, mapInToOutPos::get));

resultBuilder.addAll(Util.transform(product, ImmutableBitSet::of));
}
return resultBuilder.build();
}
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/org/apache/calcite/util/ImmutableBitSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,18 @@ public static boolean allContain(Collection<ImmutableBitSet> bitSets, int bit) {
return true;
}

/**
* Returns whether this is a minimal set with respect to the specified collection of bitSets.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you give an example in the javadoc?

public boolean isMinimal(Collection<ImmutableBitSet> bitSets) {
for (ImmutableBitSet other : bitSets) {
if (!this.equals(other) && this.contains(other)) {
return false;
}
}
return true;
}

/** Returns whether a given predicate evaluates to true for all bits in this
* set. */
public boolean allMatch(IntPredicate predicate) {
Expand Down
15 changes: 13 additions & 2 deletions core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,17 @@ private void checkColumnUniquenessForFilterWithConstantColumns(String sql) {
.withCatalogReaderFactory(factory)
.assertThatUniqueKeysAre();

sql("select key1, key1, key2, value1 from s.composite_keys_table")
.withCatalogReaderFactory(factory)
.assertThatUniqueKeysAre(bitSetOf(0, 2), bitSetOf(1, 2));
sql("select key1, key2, key2, value1 from s.composite_keys_table")
.withCatalogReaderFactory(factory)
.assertThatUniqueKeysAre(bitSetOf(0, 1), bitSetOf(0, 2));

sql("select key1, key1, key2, key2, value1 from s.composite_keys_table")
.withCatalogReaderFactory(factory)
.assertThatUniqueKeysAre(bitSetOf(0, 2), bitSetOf(0, 3), bitSetOf(1, 2), bitSetOf(1, 3));

// no column of composite keys
sql("select value1 from s.composite_keys_table")
.withCatalogReaderFactory(factory)
Expand Down Expand Up @@ -1640,7 +1651,7 @@ private static ImmutableBitSet bitSetOf(int... bits) {
@Test void calcMultipleColumnsAreUniqueCalc() {
sql("select empno, empno from emp")
.convertingProjectAsCalc()
.assertThatUniqueKeysAre(bitSetOf(0), bitSetOf(1), bitSetOf(0, 1));
.assertThatUniqueKeysAre(bitSetOf(0), bitSetOf(1));
}

@Test void calcMultipleColumnsAreUniqueCalc2() {
Expand All @@ -1655,7 +1666,7 @@ private static ImmutableBitSet bitSetOf(int... bits) {
+ " from emp a1 join emp a2\n"
+ " on (a1.empno=a2.empno)")
.convertingProjectAsCalc()
.assertThatUniqueKeysAre(bitSetOf(0), bitSetOf(1), bitSetOf(1, 2), bitSetOf(2));
.assertThatUniqueKeysAre(bitSetOf(0), bitSetOf(1), bitSetOf(2));
}

@Test void calcColumnsAreNonUniqueCalc() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,38 @@ private List<ImmutableBitSet> getSortedList() {
assertFalse(ImmutableBitSet.allContain(collection2, 4));
}

@Test void testIsMinimal() {
ImmutableBitSet se = ImmutableBitSet.of();
ImmutableBitSet s0 = ImmutableBitSet.of(0);
ImmutableBitSet s1 = ImmutableBitSet.of(1);
ImmutableBitSet s01 = ImmutableBitSet.of(0, 1);

assertTrue(se.isMinimal(Collections.singletonList(se)));
assertTrue(se.isMinimal(Collections.singletonList(s0)));
assertTrue(se.isMinimal(Collections.singletonList(s1)));
assertTrue(se.isMinimal(Collections.singletonList(s01)));

assertFalse(s0.isMinimal(Collections.singletonList(se)));
assertTrue(s0.isMinimal(Collections.singletonList(s0)));
assertTrue(s0.isMinimal(Collections.singletonList(s1)));
assertTrue(s0.isMinimal(Collections.singletonList(s01)));

assertFalse(s1.isMinimal(Collections.singletonList(se)));
assertTrue(s1.isMinimal(Collections.singletonList(s0)));
assertTrue(s1.isMinimal(Collections.singletonList(s1)));
assertTrue(s1.isMinimal(Collections.singletonList(s01)));

assertFalse(s01.isMinimal(Collections.singletonList(se)));
assertFalse(s01.isMinimal(Collections.singletonList(s0)));
assertFalse(s01.isMinimal(Collections.singletonList(s1)));
assertTrue(s01.isMinimal(Collections.singletonList(s01)));

assertTrue(se.isMinimal(Arrays.asList(s0, s1)));
assertTrue(s0.isMinimal(Arrays.asList(s0, s1)));
assertTrue(s1.isMinimal(Arrays.asList(s0, s1)));
assertFalse(s01.isMinimal(Arrays.asList(s0, s1)));
}

/**
* Test case for {@link ImmutableBitSet#anyMatch(IntPredicate)}
* and {@link ImmutableBitSet#allMatch(IntPredicate)}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ public RelMetadataFixture assertThatUniqueKeysAre(
ImmutableSortedSet.copyOf(result),
is(ImmutableSortedSet.copyOf(expectedUniqueKeys)));
checkUniqueConsistent(rel);
assertThat(result.stream().allMatch(s -> s.isMinimal(result)), is(true));
return this;
}

Expand Down
Loading