This is an automated email from the ASF dual-hosted git repository. fjy pushed a commit to branch 0.18.0 in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/0.18.0 by this push: new 908462c Eliminate common subfilters when converting it to a CNF (#9608) (#9626) 908462c is described below commit 908462c6a36cecd1c294af7276fe133952e198b4 Author: Jihoon Son <jihoon...@apache.org> AuthorDate: Mon Apr 6 21:10:30 2020 -0700 Eliminate common subfilters when converting it to a CNF (#9608) (#9626) --- .../druid/benchmark/FilterPartitionBenchmark.java | 4 +- .../apache/druid/query/filter/BooleanFilter.java | 5 +- .../apache/druid/query/filter/DimFilterUtils.java | 4 +- .../apache/druid/query/filter/TrueDimFilter.java | 8 +- .../segment/QueryableIndexStorageAdapter.java | 9 +- .../org/apache/druid/segment/filter/AndFilter.java | 33 ++-- .../org/apache/druid/segment/filter/Filters.java | 56 +++--- .../org/apache/druid/segment/filter/NotFilter.java | 32 +++- .../org/apache/druid/segment/filter/OrFilter.java | 28 ++- .../apache/druid/segment/filter/TrueFilter.java | 9 +- .../segment/join/filter/JoinFilterAnalyzer.java | 8 +- .../druid/query/filter/AndDimFilterTest.java | 29 ++- ...dDimFilterTest.java => DimFilterTestUtils.java} | 34 ++-- .../apache/druid/query/filter/OrDimFilterTest.java | 53 +++++ .../druid/segment/filter/BaseFilterTest.java | 2 +- .../druid/segment/filter/FilterPartitionTest.java | 4 +- .../filter/FilterTestUtils.java} | 38 ++-- .../apache/druid/segment/filter/FiltersTest.java | 213 ++++++++++++++++++++- ...tFilterTest.java => NotFilterEvaluateTest.java} | 6 +- .../apache/druid/segment/filter/NotFilterTest.java | 84 ++------ 20 files changed, 473 insertions(+), 186 deletions(-) diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/FilterPartitionBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/FilterPartitionBenchmark.java index b52cde1..547784e 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/FilterPartitionBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/FilterPartitionBenchmark.java @@ -377,7 +377,7 @@ public class FilterPartitionBenchmark Filter orFilter = new OrFilter(Arrays.asList(filter, filter2)); StorageAdapter sa = new QueryableIndexStorageAdapter(qIndex); - Sequence<Cursor> cursors = makeCursors(sa, Filters.convertToCNF(orFilter)); + Sequence<Cursor> cursors = makeCursors(sa, Filters.toCNF(orFilter)); readCursors(cursors, blackhole); } @@ -451,7 +451,7 @@ public class FilterPartitionBenchmark ); StorageAdapter sa = new QueryableIndexStorageAdapter(qIndex); - Sequence<Cursor> cursors = makeCursors(sa, Filters.convertToCNF(dimFilter3.toFilter())); + Sequence<Cursor> cursors = makeCursors(sa, Filters.toCNF(dimFilter3.toFilter())); readCursors(cursors, blackhole); } diff --git a/processing/src/main/java/org/apache/druid/query/filter/BooleanFilter.java b/processing/src/main/java/org/apache/druid/query/filter/BooleanFilter.java index 9bbbdb6..e11153e 100644 --- a/processing/src/main/java/org/apache/druid/query/filter/BooleanFilter.java +++ b/processing/src/main/java/org/apache/druid/query/filter/BooleanFilter.java @@ -23,12 +23,13 @@ import org.apache.druid.segment.ColumnSelector; import org.apache.druid.segment.ColumnSelectorFactory; import java.util.HashSet; -import java.util.List; import java.util.Set; public interface BooleanFilter extends Filter { - List<Filter> getFilters(); + ValueMatcher[] EMPTY_VALUE_MATCHER_ARRAY = new ValueMatcher[0]; + + Set<Filter> getFilters(); /** * Get a ValueMatcher that applies this filter to row values. diff --git a/processing/src/main/java/org/apache/druid/query/filter/DimFilterUtils.java b/processing/src/main/java/org/apache/druid/query/filter/DimFilterUtils.java index 00a84fc..c980adb 100644 --- a/processing/src/main/java/org/apache/druid/query/filter/DimFilterUtils.java +++ b/processing/src/main/java/org/apache/druid/query/filter/DimFilterUtils.java @@ -52,9 +52,9 @@ public class DimFilterUtils static final byte COLUMN_COMPARISON_CACHE_ID = 0xD; static final byte EXPRESSION_CACHE_ID = 0xE; static final byte TRUE_CACHE_ID = 0xF; - public static byte BLOOM_DIM_FILTER_CACHE_ID = 0x10; - public static final byte STRING_SEPARATOR = (byte) 0xFF; + public static final byte BLOOM_DIM_FILTER_CACHE_ID = 0x10; + public static final byte STRING_SEPARATOR = (byte) 0xFF; static byte[] computeCacheKey(byte cacheIdKey, List<DimFilter> filters) { diff --git a/processing/src/main/java/org/apache/druid/query/filter/TrueDimFilter.java b/processing/src/main/java/org/apache/druid/query/filter/TrueDimFilter.java index d10e6d9..2254358 100644 --- a/processing/src/main/java/org/apache/druid/query/filter/TrueDimFilter.java +++ b/processing/src/main/java/org/apache/druid/query/filter/TrueDimFilter.java @@ -20,9 +20,9 @@ package org.apache.druid.query.filter; import com.google.common.collect.RangeSet; +import org.apache.druid.query.cache.CacheKeyBuilder; import org.apache.druid.segment.filter.TrueFilter; -import java.nio.ByteBuffer; import java.util.Collections; import java.util.Set; @@ -32,8 +32,8 @@ public class TrueDimFilter implements DimFilter { @Override public byte[] getCacheKey() - { - return ByteBuffer.allocate(1).put(DimFilterUtils.TRUE_CACHE_ID).array(); + { + return new CacheKeyBuilder(DimFilterUtils.TRUE_CACHE_ID).build(); } @Override @@ -45,7 +45,7 @@ public class TrueDimFilter implements DimFilter @Override public Filter toFilter() { - return new TrueFilter(); + return TrueFilter.instance(); } @Override diff --git a/processing/src/main/java/org/apache/druid/segment/QueryableIndexStorageAdapter.java b/processing/src/main/java/org/apache/druid/segment/QueryableIndexStorageAdapter.java index cbb6446..70f4c9c 100644 --- a/processing/src/main/java/org/apache/druid/segment/QueryableIndexStorageAdapter.java +++ b/processing/src/main/java/org/apache/druid/segment/QueryableIndexStorageAdapter.java @@ -54,6 +54,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; /** * @@ -384,13 +385,13 @@ public class QueryableIndexStorageAdapter implements StorageAdapter * * Any subfilters that cannot be processed entirely with bitmap indexes will be moved to the post-filtering stage. */ - final List<Filter> preFilters; + final Set<Filter> preFilters; final List<Filter> postFilters = new ArrayList<>(); int preFilteredRows = totalRows; if (filter == null) { - preFilters = Collections.emptyList(); + preFilters = Collections.emptySet(); } else { - preFilters = new ArrayList<>(); + preFilters = new HashSet<>(); if (filter instanceof AndFilter) { // If we get an AndFilter, we can split the subfilters across both filtering stages @@ -432,7 +433,7 @@ public class QueryableIndexStorageAdapter implements StorageAdapter } if (queryMetrics != null) { - queryMetrics.preFilters(preFilters); + queryMetrics.preFilters(new ArrayList<>(preFilters)); queryMetrics.postFilters(postFilters); queryMetrics.reportSegmentRows(totalRows); queryMetrics.reportPreFilteredRows(preFilteredRows); diff --git a/processing/src/main/java/org/apache/druid/segment/filter/AndFilter.java b/processing/src/main/java/org/apache/druid/segment/filter/AndFilter.java index 93c890a..696bd8b 100644 --- a/processing/src/main/java/org/apache/druid/segment/filter/AndFilter.java +++ b/processing/src/main/java/org/apache/druid/segment/filter/AndFilter.java @@ -19,8 +19,10 @@ package org.apache.druid.segment.filter; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.apache.druid.collections.bitmap.ImmutableBitmap; import org.apache.druid.java.util.common.StringUtils; @@ -38,20 +40,27 @@ import org.apache.druid.segment.ColumnSelectorFactory; import org.apache.druid.segment.vector.VectorColumnSelectorFactory; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; /** */ public class AndFilter implements BooleanFilter { private static final Joiner AND_JOINER = Joiner.on(" && "); - static final ValueMatcher[] EMPTY_VALUE_MATCHER_ARRAY = new ValueMatcher[0]; - private final List<Filter> filters; + private final Set<Filter> filters; + @VisibleForTesting public AndFilter(List<Filter> filters) { + this(new HashSet<>(filters)); + } + + public AndFilter(Set<Filter> filters) + { Preconditions.checkArgument(filters.size() > 0, "Can't construct empty AndFilter"); this.filters = filters; } @@ -59,7 +68,7 @@ public class AndFilter implements BooleanFilter public static <T> ImmutableBitmap getBitmapIndex( BitmapIndexSelector selector, BitmapResultFactory<T> bitmapResultFactory, - List<Filter> filters + Set<Filter> filters ) { return bitmapResultFactory.toImmutableBitmap(getBitmapResult(selector, bitmapResultFactory, filters)); @@ -68,11 +77,11 @@ public class AndFilter implements BooleanFilter private static <T> T getBitmapResult( BitmapIndexSelector selector, BitmapResultFactory<T> bitmapResultFactory, - List<Filter> filters + Set<Filter> filters ) { if (filters.size() == 1) { - return filters.get(0).getBitmapResult(selector, bitmapResultFactory); + return Iterables.getOnlyElement(filters).getBitmapResult(selector, bitmapResultFactory); } final List<T> bitmapResults = Lists.newArrayListWithCapacity(filters.size()); @@ -102,8 +111,9 @@ public class AndFilter implements BooleanFilter { final ValueMatcher[] matchers = new ValueMatcher[filters.size()]; - for (int i = 0; i < filters.size(); i++) { - matchers[i] = filters.get(i).makeMatcher(factory); + int i = 0; + for (Filter filter : filters) { + matchers[i++] = filter.makeMatcher(factory); } return makeMatcher(matchers); } @@ -113,8 +123,9 @@ public class AndFilter implements BooleanFilter { final VectorValueMatcher[] matchers = new VectorValueMatcher[filters.size()]; - for (int i = 0; i < filters.size(); i++) { - matchers[i] = filters.get(i).makeVectorMatcher(factory); + int i = 0; + for (Filter filter : filters) { + matchers[i++] = filter.makeVectorMatcher(factory); } return makeVectorMatcher(matchers); } @@ -150,11 +161,11 @@ public class AndFilter implements BooleanFilter matchers.add(0, offsetMatcher); } - return makeMatcher(matchers.toArray(EMPTY_VALUE_MATCHER_ARRAY)); + return makeMatcher(matchers.toArray(BooleanFilter.EMPTY_VALUE_MATCHER_ARRAY)); } @Override - public List<Filter> getFilters() + public Set<Filter> getFilters() { return filters; } diff --git a/processing/src/main/java/org/apache/druid/segment/filter/Filters.java b/processing/src/main/java/org/apache/druid/segment/filter/Filters.java index 4e865f6..2328dfb 100644 --- a/processing/src/main/java/org/apache/druid/segment/filter/Filters.java +++ b/processing/src/main/java/org/apache/druid/segment/filter/Filters.java @@ -19,16 +19,14 @@ package org.apache.druid.segment.filter; -import com.google.common.base.Function; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import it.unimi.dsi.fastutil.ints.IntIterable; import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntList; import org.apache.druid.collections.bitmap.ImmutableBitmap; -import org.apache.druid.java.util.common.guava.FunctionalIterable; import org.apache.druid.query.BitmapResultFactory; import org.apache.druid.query.Query; import org.apache.druid.query.filter.BitmapIndexSelector; @@ -51,9 +49,12 @@ import javax.annotation.Nullable; import java.io.IOException; import java.io.UncheckedIOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; +import java.util.Set; +import java.util.stream.Collectors; /** * @@ -69,22 +70,9 @@ public class Filters * * @return list of Filters */ - public static List<Filter> toFilters(List<DimFilter> dimFilters) + public static Set<Filter> toFilters(List<DimFilter> dimFilters) { - return ImmutableList.copyOf( - FunctionalIterable - .create(dimFilters) - .transform( - new Function<DimFilter, Filter>() - { - @Override - public Filter apply(DimFilter input) - { - return input.toFilter(); - } - } - ) - ); + return dimFilters.stream().map(DimFilter::toFilter).collect(Collectors.toSet()); } /** @@ -438,10 +426,10 @@ public class Filters return null; } boolean useCNF = query.getContextBoolean(CTX_KEY_USE_FILTER_CNF, false); - return useCNF ? convertToCNF(filter) : filter; + return useCNF ? toCNF(filter) : filter; } - public static Filter convertToCNF(Filter current) + public static Filter toCNF(Filter current) { current = pushDownNot(current); current = flatten(current); @@ -452,7 +440,8 @@ public class Filters // CNF conversion functions were adapted from Apache Hive, see: // https://github.com/apache/hive/blob/branch-2.0/storage-api/src/java/org/apache/hadoop/hive/ql/io/sarg/SearchArgumentImpl.java - private static Filter pushDownNot(Filter current) + @VisibleForTesting + static Filter pushDownNot(Filter current) { if (current instanceof NotFilter) { Filter child = ((NotFilter) current).getBaseFilter(); @@ -460,14 +449,14 @@ public class Filters return pushDownNot(((NotFilter) child).getBaseFilter()); } if (child instanceof AndFilter) { - List<Filter> children = new ArrayList<>(); + Set<Filter> children = new HashSet<>(); for (Filter grandChild : ((AndFilter) child).getFilters()) { children.add(pushDownNot(new NotFilter(grandChild))); } return new OrFilter(children); } if (child instanceof OrFilter) { - List<Filter> children = new ArrayList<>(); + Set<Filter> children = new HashSet<>(); for (Filter grandChild : ((OrFilter) child).getFilters()) { children.add(pushDownNot(new NotFilter(grandChild))); } @@ -477,7 +466,7 @@ public class Filters if (current instanceof AndFilter) { - List<Filter> children = new ArrayList<>(); + Set<Filter> children = new HashSet<>(); for (Filter child : ((AndFilter) current).getFilters()) { children.add(pushDownNot(child)); } @@ -486,7 +475,7 @@ public class Filters if (current instanceof OrFilter) { - List<Filter> children = new ArrayList<>(); + Set<Filter> children = new HashSet<>(); for (Filter child : ((OrFilter) current).getFilters()) { children.add(pushDownNot(child)); } @@ -503,7 +492,7 @@ public class Filters return new NotFilter(convertToCNFInternal(((NotFilter) current).getBaseFilter())); } if (current instanceof AndFilter) { - List<Filter> children = new ArrayList<>(); + Set<Filter> children = new HashSet<>(); for (Filter child : ((AndFilter) current).getFilters()) { children.add(convertToCNFInternal(child)); } @@ -525,7 +514,7 @@ public class Filters } } if (!andList.isEmpty()) { - List<Filter> result = new ArrayList<>(); + Set<Filter> result = new HashSet<>(); generateAllCombinations(result, andList, nonAndList); return new AndFilter(result); } @@ -535,7 +524,8 @@ public class Filters // CNF conversion functions were adapted from Apache Hive, see: // https://github.com/apache/hive/blob/branch-2.0/storage-api/src/java/org/apache/hadoop/hive/ql/io/sarg/SearchArgumentImpl.java - private static Filter flatten(Filter root) + @VisibleForTesting + static Filter flatten(Filter root) { if (root instanceof BooleanFilter) { List<Filter> children = new ArrayList<>(((BooleanFilter) root).getFilters()); @@ -546,7 +536,7 @@ public class Filters // do we need to flatten? if (child.getClass() == root.getClass() && !(child instanceof NotFilter)) { boolean first = true; - List<Filter> grandKids = ((BooleanFilter) child).getFilters(); + Set<Filter> grandKids = ((BooleanFilter) child).getFilters(); for (Filter grandkid : grandKids) { // for the first grandkid replace the original parent if (first) { @@ -577,15 +567,15 @@ public class Filters // CNF conversion functions were adapted from Apache Hive, see: // https://github.com/apache/hive/blob/branch-2.0/storage-api/src/java/org/apache/hadoop/hive/ql/io/sarg/SearchArgumentImpl.java private static void generateAllCombinations( - List<Filter> result, + Set<Filter> result, List<Filter> andList, List<Filter> nonAndList ) { - List<Filter> children = ((AndFilter) andList.get(0)).getFilters(); + Set<Filter> children = ((AndFilter) andList.get(0)).getFilters(); if (result.isEmpty()) { for (Filter child : children) { - List<Filter> a = Lists.newArrayList(nonAndList); + Set<Filter> a = new HashSet<>(nonAndList); a.add(child); result.add(new OrFilter(a)); } @@ -594,7 +584,7 @@ public class Filters result.clear(); for (Filter child : children) { for (Filter or : work) { - List<Filter> a = Lists.newArrayList((((OrFilter) or).getFilters())); + Set<Filter> a = new HashSet<>((((OrFilter) or).getFilters())); a.add(child); result.add(new OrFilter(a)); } diff --git a/processing/src/main/java/org/apache/druid/segment/filter/NotFilter.java b/processing/src/main/java/org/apache/druid/segment/filter/NotFilter.java index 3a39e2f..a82a57f 100644 --- a/processing/src/main/java/org/apache/druid/segment/filter/NotFilter.java +++ b/processing/src/main/java/org/apache/druid/segment/filter/NotFilter.java @@ -19,6 +19,7 @@ package org.apache.druid.segment.filter; +import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.query.BitmapResultFactory; import org.apache.druid.query.filter.BitmapIndexSelector; import org.apache.druid.query.filter.Filter; @@ -32,6 +33,7 @@ import org.apache.druid.segment.ColumnSelector; import org.apache.druid.segment.ColumnSelectorFactory; import org.apache.druid.segment.vector.VectorColumnSelectorFactory; +import java.util.Objects; import java.util.Set; /** @@ -40,9 +42,7 @@ public class NotFilter implements Filter { private final Filter baseFilter; - public NotFilter( - Filter baseFilter - ) + public NotFilter(Filter baseFilter) { this.baseFilter = baseFilter; } @@ -135,6 +135,32 @@ public class NotFilter implements Filter return 1. - baseFilter.estimateSelectivity(indexSelector); } + @Override + public String toString() + { + return StringUtils.format("~(%s)", baseFilter); + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NotFilter notFilter = (NotFilter) o; + return Objects.equals(baseFilter, notFilter.baseFilter); + } + + @Override + public int hashCode() + { + // to return a different hash from baseFilter + return Objects.hash(1, baseFilter); + } + public Filter getBaseFilter() { return baseFilter; diff --git a/processing/src/main/java/org/apache/druid/segment/filter/OrFilter.java b/processing/src/main/java/org/apache/druid/segment/filter/OrFilter.java index 9e5314b..46cd9e2 100644 --- a/processing/src/main/java/org/apache/druid/segment/filter/OrFilter.java +++ b/processing/src/main/java/org/apache/druid/segment/filter/OrFilter.java @@ -19,8 +19,10 @@ package org.apache.druid.segment.filter; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; import org.apache.druid.collections.bitmap.ImmutableBitmap; import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.query.BitmapResultFactory; @@ -38,8 +40,10 @@ import org.apache.druid.segment.ColumnSelectorFactory; import org.apache.druid.segment.vector.VectorColumnSelectorFactory; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; /** */ @@ -47,10 +51,16 @@ public class OrFilter implements BooleanFilter { private static final Joiner OR_JOINER = Joiner.on(" || "); - private final List<Filter> filters; + private final Set<Filter> filters; + @VisibleForTesting public OrFilter(List<Filter> filters) { + this(new HashSet<>(filters)); + } + + public OrFilter(Set<Filter> filters) + { Preconditions.checkArgument(filters.size() > 0, "Can't construct empty OrFilter (the universe does not exist)"); this.filters = filters; @@ -60,7 +70,7 @@ public class OrFilter implements BooleanFilter public <T> T getBitmapResult(BitmapIndexSelector selector, BitmapResultFactory<T> bitmapResultFactory) { if (filters.size() == 1) { - return filters.get(0).getBitmapResult(selector, bitmapResultFactory); + return Iterables.getOnlyElement(filters).getBitmapResult(selector, bitmapResultFactory); } List<T> bitmapResults = new ArrayList<>(); @@ -76,8 +86,9 @@ public class OrFilter implements BooleanFilter { final ValueMatcher[] matchers = new ValueMatcher[filters.size()]; - for (int i = 0; i < filters.size(); i++) { - matchers[i] = filters.get(i).makeMatcher(factory); + int i = 0; + for (Filter filter : filters) { + matchers[i++] = filter.makeMatcher(factory); } return makeMatcher(matchers); } @@ -87,8 +98,9 @@ public class OrFilter implements BooleanFilter { final VectorValueMatcher[] matchers = new VectorValueMatcher[filters.size()]; - for (int i = 0; i < filters.size(); i++) { - matchers[i] = filters.get(i).makeVectorMatcher(factory); + int i = 0; + for (Filter filter : filters) { + matchers[i++] = filter.makeVectorMatcher(factory); } return makeVectorMatcher(matchers); } @@ -124,11 +136,11 @@ public class OrFilter implements BooleanFilter matchers.add(0, offsetMatcher); } - return makeMatcher(matchers.toArray(AndFilter.EMPTY_VALUE_MATCHER_ARRAY)); + return makeMatcher(matchers.toArray(BooleanFilter.EMPTY_VALUE_MATCHER_ARRAY)); } @Override - public List<Filter> getFilters() + public Set<Filter> getFilters() { return filters; } diff --git a/processing/src/main/java/org/apache/druid/segment/filter/TrueFilter.java b/processing/src/main/java/org/apache/druid/segment/filter/TrueFilter.java index 2ae0edf..58cda8c 100644 --- a/processing/src/main/java/org/apache/druid/segment/filter/TrueFilter.java +++ b/processing/src/main/java/org/apache/druid/segment/filter/TrueFilter.java @@ -33,7 +33,14 @@ import java.util.Set; */ public class TrueFilter implements Filter { - public TrueFilter() + private static final TrueFilter INSTANCE = new TrueFilter(); + + public static TrueFilter instance() + { + return INSTANCE; + } + + private TrueFilter() { } diff --git a/processing/src/main/java/org/apache/druid/segment/join/filter/JoinFilterAnalyzer.java b/processing/src/main/java/org/apache/druid/segment/join/filter/JoinFilterAnalyzer.java index d8cd4f6..892fcb3 100644 --- a/processing/src/main/java/org/apache/druid/segment/join/filter/JoinFilterAnalyzer.java +++ b/processing/src/main/java/org/apache/druid/segment/join/filter/JoinFilterAnalyzer.java @@ -128,17 +128,17 @@ public class JoinFilterAnalyzer ); } - Filter normalizedFilter = Filters.convertToCNF(originalFilter); + Filter normalizedFilter = Filters.toCNF(originalFilter); // List of candidates for pushdown // CNF normalization will generate either // - an AND filter with multiple subfilters // - or a single non-AND subfilter which cannot be split further - List<Filter> normalizedOrClauses; + Set<Filter> normalizedOrClauses; if (normalizedFilter instanceof AndFilter) { normalizedOrClauses = ((AndFilter) normalizedFilter).getFilters(); } else { - normalizedOrClauses = Collections.singletonList(normalizedFilter); + normalizedOrClauses = Collections.singleton(normalizedFilter); } List<Filter> normalizedBaseTableClauses = new ArrayList<>(); @@ -422,7 +422,7 @@ public class JoinFilterAnalyzer ) { boolean retainRhs = false; - List<Filter> newFilters = new ArrayList<>(); + Set<Filter> newFilters = new HashSet<>(); for (Filter filter : orFilter.getFilters()) { if (!areSomeColumnsFromJoin(joinFilterPreAnalysis.getJoinableClauses(), filter.getRequiredColumns())) { newFilters.add(filter); diff --git a/processing/src/test/java/org/apache/druid/query/filter/AndDimFilterTest.java b/processing/src/test/java/org/apache/druid/query/filter/AndDimFilterTest.java index f2d6c43..7aeae01 100644 --- a/processing/src/test/java/org/apache/druid/query/filter/AndDimFilterTest.java +++ b/processing/src/test/java/org/apache/druid/query/filter/AndDimFilterTest.java @@ -21,10 +21,12 @@ package org.apache.druid.query.filter; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import org.apache.druid.segment.filter.FilterTestUtils; +import org.apache.druid.testing.InitializedNullHandlingTest; import org.junit.Assert; import org.junit.Test; -public class AndDimFilterTest +public class AndDimFilterTest extends InitializedNullHandlingTest { @Test public void testGetRequiredColumns() @@ -38,4 +40,29 @@ public class AndDimFilterTest ); Assert.assertEquals(andDimFilter.getRequiredColumns(), Sets.newHashSet("a", "b", "c")); } + + @Test + public void testToFilterWithDuplicateFilters() + { + DimFilter dimFilter = DimFilterTestUtils.and( + DimFilterTestUtils.or( + DimFilterTestUtils.selector("col1", "1"), + DimFilterTestUtils.selector("col2", "2") + ), + DimFilterTestUtils.or( + // duplicate but different order + DimFilterTestUtils.selector("col2", "2"), + DimFilterTestUtils.selector("col1", "1") + ), + DimFilterTestUtils.selector("col3", "3") + ); + Filter expected = FilterTestUtils.and( + FilterTestUtils.or( + FilterTestUtils.selector("col1", "1"), + FilterTestUtils.selector("col2", "2") + ), + FilterTestUtils.selector("col3", "3") + ); + Assert.assertEquals(expected, dimFilter.toFilter()); + } } diff --git a/processing/src/test/java/org/apache/druid/query/filter/AndDimFilterTest.java b/processing/src/test/java/org/apache/druid/query/filter/DimFilterTestUtils.java similarity index 61% copy from processing/src/test/java/org/apache/druid/query/filter/AndDimFilterTest.java copy to processing/src/test/java/org/apache/druid/query/filter/DimFilterTestUtils.java index f2d6c43..87fa026 100644 --- a/processing/src/test/java/org/apache/druid/query/filter/AndDimFilterTest.java +++ b/processing/src/test/java/org/apache/druid/query/filter/DimFilterTestUtils.java @@ -19,23 +19,27 @@ package org.apache.druid.query.filter; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import org.junit.Assert; -import org.junit.Test; +import java.util.Arrays; -public class AndDimFilterTest +public class DimFilterTestUtils { - @Test - public void testGetRequiredColumns() + public static AndDimFilter and(DimFilter... filters) { - AndDimFilter andDimFilter = new AndDimFilter( - Lists.newArrayList( - new SelectorDimFilter("a", "d", null), - new SelectorDimFilter("b", "d", null), - new SelectorDimFilter("c", "d", null) - ) - ); - Assert.assertEquals(andDimFilter.getRequiredColumns(), Sets.newHashSet("a", "b", "c")); + return new AndDimFilter(Arrays.asList(filters)); + } + + public static OrDimFilter or(DimFilter... filters) + { + return new OrDimFilter(Arrays.asList(filters)); + } + + public static NotDimFilter not(DimFilter filter) + { + return new NotDimFilter(filter); + } + + public static SelectorDimFilter selector(final String fieldName, final String value) + { + return new SelectorDimFilter(fieldName, value, null); } } diff --git a/processing/src/test/java/org/apache/druid/query/filter/OrDimFilterTest.java b/processing/src/test/java/org/apache/druid/query/filter/OrDimFilterTest.java new file mode 100644 index 0000000..9f02194 --- /dev/null +++ b/processing/src/test/java/org/apache/druid/query/filter/OrDimFilterTest.java @@ -0,0 +1,53 @@ +/* + * 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.query.filter; + +import org.apache.druid.segment.filter.FilterTestUtils; +import org.apache.druid.testing.InitializedNullHandlingTest; +import org.junit.Assert; +import org.junit.Test; + +public class OrDimFilterTest extends InitializedNullHandlingTest +{ + @Test + public void testToFilterWithDuplicateFilters() + { + DimFilter dimFilter = DimFilterTestUtils.or( + DimFilterTestUtils.and( + DimFilterTestUtils.selector("col1", "1"), + DimFilterTestUtils.selector("col2", "2") + ), + DimFilterTestUtils.and( + // duplicate but different order + DimFilterTestUtils.selector("col2", "2"), + DimFilterTestUtils.selector("col1", "1") + ), + DimFilterTestUtils.selector("col3", "3") + ); + Filter expected = FilterTestUtils.or( + FilterTestUtils.and( + FilterTestUtils.selector("col1", "1"), + FilterTestUtils.selector("col2", "2") + ), + FilterTestUtils.selector("col3", "3") + ); + Assert.assertEquals(expected, dimFilter.toFilter()); + } +} diff --git a/processing/src/test/java/org/apache/druid/segment/filter/BaseFilterTest.java b/processing/src/test/java/org/apache/druid/segment/filter/BaseFilterTest.java index 8a704d2..5700458 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/BaseFilterTest.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/BaseFilterTest.java @@ -336,7 +336,7 @@ public abstract class BaseFilterTest extends InitializedNullHandlingTest final DimFilter maybeOptimized = optimize ? dimFilter.optimize() : dimFilter; final Filter filter = maybeOptimized.toFilter(); - return cnf ? Filters.convertToCNF(filter) : filter; + return cnf ? Filters.toCNF(filter) : filter; } private DimFilter maybeOptimize(final DimFilter dimFilter) diff --git a/processing/src/test/java/org/apache/druid/segment/filter/FilterPartitionTest.java b/processing/src/test/java/org/apache/druid/segment/filter/FilterPartitionTest.java index bb76c54..5ffd167 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/FilterPartitionTest.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/FilterPartitionTest.java @@ -621,7 +621,7 @@ public class FilterPartitionTest extends BaseFilterTest ); Filter filter1 = dimFilter1.toFilter(); - Filter filter1CNF = Filters.convertToCNF(filter1); + Filter filter1CNF = Filters.toCNF(filter1); Assert.assertEquals(AndFilter.class, filter1CNF.getClass()); Assert.assertEquals(2, ((AndFilter) filter1CNF).getFilters().size()); @@ -675,7 +675,7 @@ public class FilterPartitionTest extends BaseFilterTest ); Filter filter1 = dimFilter1.toFilter(); - Filter filter1CNF = Filters.convertToCNF(filter1); + Filter filter1CNF = Filters.toCNF(filter1); Assert.assertEquals(AndFilter.class, filter1CNF.getClass()); Assert.assertEquals(2, ((AndFilter) filter1CNF).getFilters().size()); diff --git a/processing/src/test/java/org/apache/druid/query/filter/AndDimFilterTest.java b/processing/src/test/java/org/apache/druid/segment/filter/FilterTestUtils.java similarity index 58% copy from processing/src/test/java/org/apache/druid/query/filter/AndDimFilterTest.java copy to processing/src/test/java/org/apache/druid/segment/filter/FilterTestUtils.java index f2d6c43..003d8d8 100644 --- a/processing/src/test/java/org/apache/druid/query/filter/AndDimFilterTest.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/FilterTestUtils.java @@ -17,25 +17,31 @@ * under the License. */ -package org.apache.druid.query.filter; +package org.apache.druid.segment.filter; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import org.junit.Assert; -import org.junit.Test; +import org.apache.druid.query.filter.Filter; -public class AndDimFilterTest +import java.util.Arrays; + +public class FilterTestUtils { - @Test - public void testGetRequiredColumns() + public static AndFilter and(Filter... filters) + { + return new AndFilter(Arrays.asList(filters)); + } + + public static OrFilter or(Filter... filters) + { + return new OrFilter(Arrays.asList(filters)); + } + + public static NotFilter not(Filter filter) + { + return new NotFilter(filter); + } + + public static SelectorFilter selector(final String fieldName, final String value) { - AndDimFilter andDimFilter = new AndDimFilter( - Lists.newArrayList( - new SelectorDimFilter("a", "d", null), - new SelectorDimFilter("b", "d", null), - new SelectorDimFilter("c", "d", null) - ) - ); - Assert.assertEquals(andDimFilter.getRequiredColumns(), Sets.newHashSet("a", "b", "c")); + return new SelectorFilter(fieldName, value, null); } } diff --git a/processing/src/test/java/org/apache/druid/segment/filter/FiltersTest.java b/processing/src/test/java/org/apache/druid/segment/filter/FiltersTest.java index 56b993c..70b527d 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/FiltersTest.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/FiltersTest.java @@ -25,14 +25,16 @@ import org.apache.druid.collections.bitmap.BitmapFactory; import org.apache.druid.collections.bitmap.ConciseBitmapFactory; import org.apache.druid.collections.bitmap.ImmutableBitmap; import org.apache.druid.collections.bitmap.MutableBitmap; +import org.apache.druid.query.filter.Filter; import org.apache.druid.segment.IntIteratorUtils; import org.apache.druid.segment.column.BitmapIndex; +import org.apache.druid.testing.InitializedNullHandlingTest; import org.junit.Assert; import org.junit.Test; import java.util.List; -public class FiltersTest +public class FiltersTest extends InitializedNullHandlingTest { @Test public void testEstimateSelectivityOfBitmapList() @@ -50,6 +52,215 @@ public class FiltersTest Assert.assertEquals(expected, estimated, 0.00001); } + @Test + public void testPushDownNot() + { + final Filter filter = FilterTestUtils.not( + FilterTestUtils.and( + FilterTestUtils.selector("col1", "1"), + FilterTestUtils.selector("col2", "2"), + FilterTestUtils.not(FilterTestUtils.selector("col3", "3")) + ) + ); + final Filter expected = FilterTestUtils.or( + FilterTestUtils.not(FilterTestUtils.selector("col1", "1")), + FilterTestUtils.not(FilterTestUtils.selector("col2", "2")), + FilterTestUtils.selector("col3", "3") + ); + Assert.assertEquals(expected, Filters.pushDownNot(filter)); + } + + @Test + public void testPushDownNotLeafNot() + { + final Filter filter = FilterTestUtils.and( + FilterTestUtils.selector("col1", "1"), + FilterTestUtils.selector("col2", "2"), + FilterTestUtils.not(FilterTestUtils.selector("col3", "3")) + ); + Assert.assertEquals(filter, Filters.pushDownNot(filter)); + } + + @Test + public void testFlatten() + { + final Filter filter = FilterTestUtils.and( + FilterTestUtils.and( + FilterTestUtils.and( + FilterTestUtils.selector("col1", "1"), + FilterTestUtils.selector("col2", "2") + ) + ), + FilterTestUtils.selector("col3", "3") + ); + final Filter expected = FilterTestUtils.and( + FilterTestUtils.selector("col1", "1"), + FilterTestUtils.selector("col2", "2"), + FilterTestUtils.selector("col3", "3") + ); + Assert.assertEquals(expected, Filters.flatten(filter)); + } + + @Test + public void testFlattenUnflattenable() + { + final Filter filter = FilterTestUtils.and( + FilterTestUtils.or( + FilterTestUtils.selector("col1", "1"), + FilterTestUtils.selector("col2", "2") + ), + FilterTestUtils.selector("col3", "3") + ); + Assert.assertEquals(filter, Filters.flatten(filter)); + } + + @Test + public void testToCNFWithMuchReducibleFilter() + { + final Filter muchReducible = FilterTestUtils.and( + // should be flattened + FilterTestUtils.and( + FilterTestUtils.and( + FilterTestUtils.and(FilterTestUtils.selector("col1", "val1")) + ) + ), + // should be flattened + FilterTestUtils.and( + FilterTestUtils.or( + FilterTestUtils.and(FilterTestUtils.selector("col1", "val1")) + ) + ), + // should be flattened + FilterTestUtils.or( + FilterTestUtils.and( + FilterTestUtils.or(FilterTestUtils.selector("col1", "val1")) + ) + ), + // should eliminate duplicate filters + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.selector("col2", "val2"), + FilterTestUtils.and( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.selector("col2", "val2") + ), + FilterTestUtils.and( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.and( + FilterTestUtils.selector("col2", "val2"), + FilterTestUtils.selector("col1", "val1") + ) + ) + ); + final Filter expected = FilterTestUtils.and( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.selector("col2", "val2") + ); + Assert.assertEquals(expected, Filters.toCNF(muchReducible)); + } + + @Test + public void testToCNFWithComplexFilterIncludingNotAndOr() + { + final Filter filter = FilterTestUtils.and( + FilterTestUtils.or( + FilterTestUtils.and( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.selector("col2", "val2") + ), + FilterTestUtils.not( + FilterTestUtils.and( + FilterTestUtils.selector("col4", "val4"), + FilterTestUtils.selector("col5", "val5") + ) + ) + ), + FilterTestUtils.or( + FilterTestUtils.not( + FilterTestUtils.or( + FilterTestUtils.selector("col2", "val2"), + FilterTestUtils.selector("col4", "val4"), + FilterTestUtils.selector("col5", "val5") + ) + ), + FilterTestUtils.and( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.selector("col3", "val3") + ) + ), + FilterTestUtils.and( + FilterTestUtils.or( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.selector("col2", "val22"), // selecting different value + FilterTestUtils.selector("col3", "val3") + ), + FilterTestUtils.not( + FilterTestUtils.selector("col1", "val11") + ) + ), + FilterTestUtils.and( + FilterTestUtils.or( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.selector("col2", "val22"), + FilterTestUtils.selector("col3", "val3") + ), + FilterTestUtils.not( + FilterTestUtils.selector("col1", "val11") // selecting different value + ) + ) + ); + final Filter expected = FilterTestUtils.and( + FilterTestUtils.or( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.selector("col2", "val22"), + FilterTestUtils.selector("col3", "val3") + ), + FilterTestUtils.or( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.not(FilterTestUtils.selector("col2", "val2")) + ), + FilterTestUtils.or( + FilterTestUtils.not(FilterTestUtils.selector("col2", "val2")), + FilterTestUtils.selector("col3", "val3") + ), + FilterTestUtils.or( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.not(FilterTestUtils.selector("col4", "val4")) + ), + FilterTestUtils.or( + FilterTestUtils.selector("col3", "val3"), + FilterTestUtils.not(FilterTestUtils.selector("col4", "val4")) + ), + FilterTestUtils.or( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.not(FilterTestUtils.selector("col5", "val5")) + ), + FilterTestUtils.or( + FilterTestUtils.selector("col3", "val3"), + FilterTestUtils.not(FilterTestUtils.selector("col5", "val5")) + ), + FilterTestUtils.not(FilterTestUtils.selector("col1", "val11")), + // The below OR filter could be eliminated because this filter also has + // (col1 = val1 || ~(col4 = val4)) && (col1 = val1 || ~(col5 = val5)). + // The reduction process would be + // (col1 = val1 || ~(col4 = val4)) && (col1 = val1 || ~(col5 = val5)) && (col1 = val1 || ~(col4 = val4) || ~(col5 = val5)) + // => (col1 = val1 && ~(col4 = val4) || ~(col5 = val5)) && (col1 = val1 || ~(col4 = val4) || ~(col5 = val5)) + // => (col1 = val1 && ~(col4 = val4) || ~(col5 = val5)) + // => (col1 = val1 || ~(col4 = val4)) && (col1 = val1 || ~(col5 = val5)). + // However, we don't have this reduction now, so we have a filter in a suboptimized CNF. + FilterTestUtils.or( + FilterTestUtils.selector("col1", "val1"), + FilterTestUtils.not(FilterTestUtils.selector("col4", "val4")), + FilterTestUtils.not(FilterTestUtils.selector("col5", "val5")) + ), + FilterTestUtils.or( + FilterTestUtils.selector("col2", "val2"), + FilterTestUtils.not(FilterTestUtils.selector("col4", "val4")), + FilterTestUtils.not(FilterTestUtils.selector("col5", "val5")) + ) + ); + Assert.assertEquals(expected, Filters.toCNF(filter)); + } + private static BitmapIndex getBitmapIndex(final List<ImmutableBitmap> bitmapList) { return new BitmapIndex() diff --git a/processing/src/test/java/org/apache/druid/segment/filter/NotFilterTest.java b/processing/src/test/java/org/apache/druid/segment/filter/NotFilterEvaluateTest.java similarity index 95% copy from processing/src/test/java/org/apache/druid/segment/filter/NotFilterTest.java copy to processing/src/test/java/org/apache/druid/segment/filter/NotFilterEvaluateTest.java index f512d70..a2f073c 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/NotFilterTest.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/NotFilterEvaluateTest.java @@ -44,7 +44,7 @@ import java.util.List; import java.util.Map; @RunWith(Parameterized.class) -public class NotFilterTest extends BaseFilterTest +public class NotFilterEvaluateTest extends BaseFilterTest { private static final String TIMESTAMP_COLUMN = "timestamp"; @@ -64,7 +64,7 @@ public class NotFilterTest extends BaseFilterTest PARSER.parseBatch(ImmutableMap.of("dim0", "5")).get(0) ); - public NotFilterTest( + public NotFilterEvaluateTest( String testName, IndexBuilder indexBuilder, Function<IndexBuilder, Pair<StorageAdapter, Closeable>> finisher, @@ -78,7 +78,7 @@ public class NotFilterTest extends BaseFilterTest @AfterClass public static void tearDown() throws Exception { - BaseFilterTest.tearDown(NotFilterTest.class.getName()); + BaseFilterTest.tearDown(NotFilterEvaluateTest.class.getName()); } @Test diff --git a/processing/src/test/java/org/apache/druid/segment/filter/NotFilterTest.java b/processing/src/test/java/org/apache/druid/segment/filter/NotFilterTest.java index f512d70..d3f34e1 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/NotFilterTest.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/NotFilterTest.java @@ -19,86 +19,24 @@ package org.apache.druid.segment.filter; -import com.google.common.base.Function; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import org.apache.druid.data.input.InputRow; -import org.apache.druid.data.input.impl.DimensionsSpec; -import org.apache.druid.data.input.impl.InputRowParser; -import org.apache.druid.data.input.impl.MapInputRowParser; -import org.apache.druid.data.input.impl.TimeAndDimsParseSpec; -import org.apache.druid.data.input.impl.TimestampSpec; -import org.apache.druid.java.util.common.DateTimes; -import org.apache.druid.java.util.common.Pair; -import org.apache.druid.query.filter.NotDimFilter; -import org.apache.druid.query.filter.SelectorDimFilter; -import org.apache.druid.segment.IndexBuilder; -import org.apache.druid.segment.StorageAdapter; -import org.junit.AfterClass; +import nl.jqno.equalsverifier.EqualsVerifier; +import org.apache.druid.query.filter.Filter; +import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import java.io.Closeable; -import java.util.List; -import java.util.Map; - -@RunWith(Parameterized.class) -public class NotFilterTest extends BaseFilterTest +public class NotFilterTest { - private static final String TIMESTAMP_COLUMN = "timestamp"; - - private static final InputRowParser<Map<String, Object>> PARSER = new MapInputRowParser( - new TimeAndDimsParseSpec( - new TimestampSpec(TIMESTAMP_COLUMN, "iso", DateTimes.of("2000")), - new DimensionsSpec(null, null, null) - ) - ); - - private static final List<InputRow> ROWS = ImmutableList.of( - PARSER.parseBatch(ImmutableMap.of("dim0", "0")).get(0), - PARSER.parseBatch(ImmutableMap.of("dim0", "1")).get(0), - PARSER.parseBatch(ImmutableMap.of("dim0", "2")).get(0), - PARSER.parseBatch(ImmutableMap.of("dim0", "3")).get(0), - PARSER.parseBatch(ImmutableMap.of("dim0", "4")).get(0), - PARSER.parseBatch(ImmutableMap.of("dim0", "5")).get(0) - ); - - public NotFilterTest( - String testName, - IndexBuilder indexBuilder, - Function<IndexBuilder, Pair<StorageAdapter, Closeable>> finisher, - boolean cnf, - boolean optimize - ) - { - super(testName, ROWS, indexBuilder, finisher, cnf, optimize); - } - - @AfterClass - public static void tearDown() throws Exception + @Test + public void testEquals() { - BaseFilterTest.tearDown(NotFilterTest.class.getName()); + EqualsVerifier.forClass(NotFilter.class).usingGetClass().withNonnullFields("baseFilter").verify(); } @Test - public void testNotSelector() + public void testHashCodeCompareWithBaseFilter() { - assertFilterMatches( - new NotDimFilter(new SelectorDimFilter("dim0", null, null)), - ImmutableList.of("0", "1", "2", "3", "4", "5") - ); - assertFilterMatches( - new NotDimFilter(new SelectorDimFilter("dim0", "", null)), - ImmutableList.of("0", "1", "2", "3", "4", "5") - ); - assertFilterMatches( - new NotDimFilter(new SelectorDimFilter("dim0", "0", null)), - ImmutableList.of("1", "2", "3", "4", "5") - ); - assertFilterMatches( - new NotDimFilter(new SelectorDimFilter("dim0", "1", null)), - ImmutableList.of("0", "2", "3", "4", "5") - ); + final Filter baseFilter = FilterTestUtils.selector("col1", "1"); + final Filter notFilter = FilterTestUtils.not(baseFilter); + Assert.assertNotEquals(notFilter.hashCode(), baseFilter.hashCode()); } } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@druid.apache.org For additional commands, e-mail: commits-h...@druid.apache.org