This is an automated email from the ASF dual-hosted git repository.
cwylie 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 480a947e8b6 Fail projection filter match early if any child in
`AndFilter` failed rewrite (#18407)
480a947e8b6 is described below
commit 480a947e8b6afa6f2637954928fa82da15c05037
Author: Cece Mei <[email protected]>
AuthorDate: Tue Aug 26 23:31:28 2025 +0200
Fail projection filter match early if any child in `AndFilter` failed
rewrite (#18407)
---
.../segment/projections/ProjectionFilterMatch.java | 91 ++++++++++++++-
.../druid/segment/projections/Projections.java | 85 +-------------
.../projections/ProjectionFilterMatchTest.java | 130 +++++++++++++++++++++
.../druid/segment/projections/ProjectionsTest.java | 64 ----------
4 files changed, 220 insertions(+), 150 deletions(-)
diff --git
a/processing/src/main/java/org/apache/druid/segment/projections/ProjectionFilterMatch.java
b/processing/src/main/java/org/apache/druid/segment/projections/ProjectionFilterMatch.java
index ab4b79624bd..0b5dfce7bdf 100644
---
a/processing/src/main/java/org/apache/druid/segment/projections/ProjectionFilterMatch.java
+++
b/processing/src/main/java/org/apache/druid/segment/projections/ProjectionFilterMatch.java
@@ -19,12 +19,17 @@
package org.apache.druid.segment.projections;
+import com.google.common.collect.Lists;
import org.apache.druid.query.filter.Filter;
+import org.apache.druid.segment.filter.AndFilter;
+import org.apache.druid.segment.filter.IsBooleanFilter;
import org.apache.druid.segment.filter.TrueFilter;
+import javax.annotation.Nullable;
+import java.util.List;
+
/**
- * Terminal marker used by {@link Projections#rewriteFilter(Filter, Filter)}
indicating that a {@link Filter} can be
- * dropped because it matches the filter of a projection
+ * Terminal marker indicating that a {@link Filter} can be dropped because it
matches the filter of a projection
*/
public final class ProjectionFilterMatch extends TrueFilter
{
@@ -34,4 +39,86 @@ public final class ProjectionFilterMatch extends TrueFilter
{
// no instantiation
}
+
+ /**
+ * Rewrites a query {@link Filter} if possible, removing the {@link Filter}
of a projection. To match a projection
+ * filter, the query filter must be equal to the projection filter, or must
contain the projection filter as the child
+ * of an AND filter. This method returns null
+ * indicating that a rewrite is impossible with the implication that the
query cannot use the projection because the
+ * projection doesn't contain all the rows the query would match if not
using the projection.
+ */
+ @Nullable
+ public static Filter rewriteFilter(Filter projectionFilter, Filter
queryFilter)
+ {
+ if (projectionFilter.equals(queryFilter)) {
+ return ProjectionFilterMatch.INSTANCE;
+ }
+ if (queryFilter instanceof IsBooleanFilter && ((IsBooleanFilter)
queryFilter).isTrue()) {
+ final IsBooleanFilter isTrueFilter = (IsBooleanFilter) queryFilter;
+ final Filter rewritten = rewriteFilter(projectionFilter,
isTrueFilter.getBaseFilter());
+ if (rewritten == null) {
+ return null;
+ }
+ //noinspection ObjectEquality
+ if (rewritten == ProjectionFilterMatch.INSTANCE) {
+ return ProjectionFilterMatch.INSTANCE;
+ }
+ return new IsBooleanFilter(rewritten, true);
+ }
+ if (queryFilter instanceof AndFilter) {
+ AndFilter andFilter = (AndFilter) queryFilter;
+
+ // if both and filters, check to see if the query and filter contains
all of the clauses of the projection and filter
+ if (projectionFilter instanceof AndFilter) {
+ AndFilter projectionAndFilter = (AndFilter) projectionFilter;
+ Filter rewritten = andFilter;
+ // calling rewriteFilter using each child of the projection AND filter
as the projection filter will remove
+ // the child from the query AND filter if it exists (or return null if
it does not exist, since it must exist
+ // to be a valid rewrite). The remaining AND filter of will only
contain children that were not part of the
+ // projection AND filter
+ for (Filter filter : projectionAndFilter.getFilters()) {
+ rewritten = rewriteFilter(filter, rewritten);
+ if (rewritten != null) {
+ if (rewritten == ProjectionFilterMatch.INSTANCE) {
+ return ProjectionFilterMatch.INSTANCE;
+ }
+ } else {
+ return null;
+ }
+ }
+ if (rewritten != null) {
+ return rewritten;
+ }
+ return null;
+ }
+
+ // else check to see if any clause of the query AND filter is the
projection filter
+ List<Filter> newChildren =
Lists.newArrayListWithExpectedSize(andFilter.getFilters().size());
+ boolean childRewritten = false;
+ for (Filter filter : andFilter.getFilters()) {
+ Filter rewritten = rewriteFilter(projectionFilter, filter);
+ //noinspection ObjectEquality
+ if (rewritten == ProjectionFilterMatch.INSTANCE) {
+ childRewritten = true;
+ } else {
+ if (rewritten != null) {
+ newChildren.add(rewritten);
+ childRewritten = true;
+ } else {
+ newChildren.add(filter);
+ }
+ }
+ }
+ // at least one child must have been rewritten to rewrite the AND
+ if (childRewritten) {
+ if (newChildren.size() > 1) {
+ return new AndFilter(newChildren);
+ } else {
+ return newChildren.get(0);
+ }
+ }
+ return null;
+ }
+ return null;
+ }
}
diff --git
a/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
b/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
index 47258886017..7b280544abc 100644
---
a/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
+++
b/processing/src/main/java/org/apache/druid/segment/projections/Projections.java
@@ -19,7 +19,6 @@
package org.apache.druid.segment.projections;
-import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.druid.data.input.impl.AggregateProjectionSpec;
import org.apache.druid.error.InvalidInput;
@@ -33,8 +32,6 @@ import org.apache.druid.segment.CursorBuildSpec;
import org.apache.druid.segment.CursorHolder;
import org.apache.druid.segment.VirtualColumn;
import org.apache.druid.segment.column.ColumnHolder;
-import org.apache.druid.segment.filter.AndFilter;
-import org.apache.druid.segment.filter.IsBooleanFilter;
import org.apache.druid.utils.CollectionUtils;
import javax.annotation.Nullable;
@@ -198,7 +195,7 @@ public class Projections
final Filter remappedQueryFilter =
queryFilter.rewriteRequiredColumns(filterRewrites);
- final Filter rewritten = rewriteFilter(projectionFilter,
remappedQueryFilter);
+ final Filter rewritten =
ProjectionFilterMatch.rewriteFilter(projectionFilter, remappedQueryFilter);
// if the filter does not contain the projection filter, we cannot
match this projection
if (rewritten == null) {
return null;
@@ -472,86 +469,6 @@ public class Projections
return null;
}
- /**
- * Rewrites a query {@link Filter} if possible, removing the {@link Filter}
of a projection. To match a projection
- * filter, the query filter must be equal to the projection filter, or must
contain the projection filter as the child
- * of an AND filter. This method returns null
- * indicating that a rewrite is impossible with the implication that the
query cannot use the projection because the
- * projection doesn't contain all the rows the query would match if not
using the projection.
- */
- @Nullable
- public static Filter rewriteFilter(Filter projectionFilter, Filter
queryFilter)
- {
- if (queryFilter.equals(projectionFilter)) {
- return ProjectionFilterMatch.INSTANCE;
- }
- if (queryFilter instanceof IsBooleanFilter && ((IsBooleanFilter)
queryFilter).isTrue()) {
- final IsBooleanFilter isTrueFilter = (IsBooleanFilter) queryFilter;
- final Filter rewritten = rewriteFilter(projectionFilter,
isTrueFilter.getBaseFilter());
- if (rewritten == null) {
- return null;
- }
- //noinspection ObjectEquality
- if (rewritten == ProjectionFilterMatch.INSTANCE) {
- return ProjectionFilterMatch.INSTANCE;
- }
- return new IsBooleanFilter(rewritten, true);
- }
- if (queryFilter instanceof AndFilter) {
- AndFilter andFilter = (AndFilter) queryFilter;
-
- // if both and filters, check to see if the query and filter contains
all of the clauses of the projection and filter
- if (projectionFilter instanceof AndFilter) {
- AndFilter projectionAndFilter = (AndFilter) projectionFilter;
- Filter rewritten = andFilter;
- // calling rewriteFilter using each child of the projection AND filter
as the projection filter will remove
- // the child from the query AND filter if it exists (or return null if
it does not exist, since it must exist
- // to be a valid rewrite). The remaining AND filter of will only
contain children that were not part of the
- // projection AND filter
- for (Filter filter : projectionAndFilter.getFilters()) {
- rewritten = rewriteFilter(filter, rewritten);
- if (rewritten != null) {
- if (rewritten == ProjectionFilterMatch.INSTANCE) {
- return ProjectionFilterMatch.INSTANCE;
- }
- }
- }
- if (rewritten != null) {
- return rewritten;
- }
- return null;
- }
-
- // else check to see if any clause of the query AND filter is the
projection filter
- List<Filter> newChildren =
Lists.newArrayListWithExpectedSize(andFilter.getFilters().size());
- boolean childRewritten = false;
- for (Filter filter : andFilter.getFilters()) {
- Filter rewritten = rewriteFilter(projectionFilter, filter);
- //noinspection ObjectEquality
- if (rewritten == ProjectionFilterMatch.INSTANCE) {
- childRewritten = true;
- } else {
- if (rewritten != null) {
- newChildren.add(rewritten);
- childRewritten = true;
- } else {
- newChildren.add(filter);
- }
- }
- }
- // at least one child must have been rewritten to rewrite the AND
- if (childRewritten) {
- if (newChildren.size() > 1) {
- return new AndFilter(newChildren);
- } else {
- return newChildren.get(0);
- }
- }
- return null;
- }
- return null;
- }
-
public static String
getProjectionSmooshV9FileName(AggregateProjectionMetadata projectionSpec,
String columnName)
{
return getProjectionSmooshV9Prefix(projectionSpec) + columnName;
diff --git
a/processing/src/test/java/org/apache/druid/segment/projections/ProjectionFilterMatchTest.java
b/processing/src/test/java/org/apache/druid/segment/projections/ProjectionFilterMatchTest.java
new file mode 100644
index 00000000000..91494a7608b
--- /dev/null
+++
b/processing/src/test/java/org/apache/druid/segment/projections/ProjectionFilterMatchTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.projections;
+
+import org.apache.druid.query.filter.EqualityFilter;
+import org.apache.druid.query.filter.Filter;
+import org.apache.druid.segment.column.ColumnType;
+import org.apache.druid.segment.filter.AndFilter;
+import org.apache.druid.segment.filter.IsBooleanFilter;
+import org.apache.druid.segment.filter.OrFilter;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class ProjectionFilterMatchTest
+{
+ @Test
+ void testRewriteFilter()
+ {
+ Filter xeqfoo = new EqualityFilter("x", ColumnType.STRING, "foo", null);
+ Filter xeqfoo2 = new EqualityFilter("x", ColumnType.STRING, "foo", null);
+ Filter xeqbar = new EqualityFilter("x", ColumnType.STRING, "bar", null);
+ Filter yeqbar = new EqualityFilter("y", ColumnType.STRING, "bar", null);
+ Filter zeq123 = new EqualityFilter("z", ColumnType.LONG, 123L, null);
+
+ Filter queryFilter = xeqfoo2;
+ Assertions.assertInstanceOf(
+ ProjectionFilterMatch.class,
+ ProjectionFilterMatch.rewriteFilter(xeqfoo, queryFilter)
+ );
+
+ queryFilter = yeqbar;
+ Assertions.assertNull(ProjectionFilterMatch.rewriteFilter(xeqfoo,
queryFilter));
+
+ queryFilter = new AndFilter(List.of(xeqfoo, yeqbar));
+ Assertions.assertEquals(
+ yeqbar,
+ ProjectionFilterMatch.rewriteFilter(xeqfoo, queryFilter)
+ );
+
+ queryFilter = new AndFilter(List.of(new OrFilter(List.of(xeqfoo, xeqbar)),
yeqbar));
+ Assertions.assertNull(ProjectionFilterMatch.rewriteFilter(xeqfoo,
queryFilter));
+
+ queryFilter = new AndFilter(List.of(new IsBooleanFilter(xeqfoo, true),
yeqbar));
+ Assertions.assertEquals(yeqbar,
ProjectionFilterMatch.rewriteFilter(xeqfoo, queryFilter));
+
+ queryFilter = new AndFilter(List.of(new IsBooleanFilter(xeqfoo, false),
yeqbar));
+ Assertions.assertNull(ProjectionFilterMatch.rewriteFilter(xeqfoo,
queryFilter));
+
+ queryFilter = new AndFilter(List.of(new AndFilter(List.of(xeqfoo,
yeqbar)), zeq123));
+ Assertions.assertEquals(
+ new AndFilter(List.of(yeqbar, zeq123)),
+ ProjectionFilterMatch.rewriteFilter(xeqfoo, queryFilter)
+ );
+
+ queryFilter = new AndFilter(
+ List.of(
+ new EqualityFilter("a", ColumnType.STRING, "foo", null),
+ new EqualityFilter("b", ColumnType.STRING, "bar", null),
+ new EqualityFilter("c", ColumnType.STRING, "baz", null)
+ )
+ );
+ Assertions.assertEquals(
+ new EqualityFilter("b", ColumnType.STRING, "bar", null),
+ ProjectionFilterMatch.rewriteFilter(
+ new AndFilter(
+ List.of(
+ new EqualityFilter("a", ColumnType.STRING, "foo", null),
+ new EqualityFilter("c", ColumnType.STRING, "baz", null)
+ )
+ ),
+ queryFilter
+ )
+ );
+ }
+
+ @Test
+ void testRewriteFilter_andFilterNotMatch()
+ {
+ Filter xeqfoo = new EqualityFilter("x", ColumnType.STRING, "foo", null);
+ Filter metricLike = new EqualityFilter("metric", ColumnType.STRING,
"metric1", null);
+ Filter envOrUser = new OrFilter(List.of(
+ new EqualityFilter("env", ColumnType.STRING, "dev", null),
+ new EqualityFilter("user", ColumnType.STRING, "user-a", null)
+ ));
+ Filter envOrUser2 = new OrFilter(List.of(
+ new EqualityFilter("prod", ColumnType.STRING, "dev", null),
+ new EqualityFilter("another-user", ColumnType.STRING, "user-a", null)
+ ));
+
+ Filter queryFilter = new AndFilter(List.of(xeqfoo, envOrUser, metricLike));
+ Filter projectionFilter = new AndFilter(List.of(envOrUser2, metricLike));
+
+
Assertions.assertNull(ProjectionFilterMatch.rewriteFilter(projectionFilter,
queryFilter));
+ }
+
+ @Test
+ void testRewriteFilter_andFilterMatch()
+ {
+ Filter xeqfoo = new EqualityFilter("x", ColumnType.STRING, "foo", null);
+ Filter metricLike = new EqualityFilter("metric", ColumnType.STRING,
"metric1", null);
+ Filter envOrUser = new OrFilter(List.of(
+ new EqualityFilter("env", ColumnType.STRING, "dev", null),
+ new EqualityFilter("user", ColumnType.STRING, "user-a", null)
+ ));
+
+ Filter queryFilter = new AndFilter(List.of(xeqfoo, envOrUser, metricLike));
+ Filter projectionFilter = new AndFilter(List.of(envOrUser, metricLike));
+
+ Assertions.assertEquals(xeqfoo,
ProjectionFilterMatch.rewriteFilter(projectionFilter, queryFilter));
+ }
+}
diff --git
a/processing/src/test/java/org/apache/druid/segment/projections/ProjectionsTest.java
b/processing/src/test/java/org/apache/druid/segment/projections/ProjectionsTest.java
index 3c13a0571bf..dc86fb8ab2f 100644
---
a/processing/src/test/java/org/apache/druid/segment/projections/ProjectionsTest.java
+++
b/processing/src/test/java/org/apache/druid/segment/projections/ProjectionsTest.java
@@ -24,15 +24,11 @@ import org.apache.druid.data.input.impl.LongDimensionSchema;
import org.apache.druid.data.input.impl.StringDimensionSchema;
import org.apache.druid.query.aggregation.LongSumAggregatorFactory;
import org.apache.druid.query.filter.EqualityFilter;
-import org.apache.druid.query.filter.Filter;
import org.apache.druid.query.filter.LikeDimFilter;
import org.apache.druid.segment.AggregateProjectionMetadata;
import org.apache.druid.segment.CursorBuildSpec;
import org.apache.druid.segment.column.ColumnType;
import org.apache.druid.segment.column.RowSignature;
-import org.apache.druid.segment.filter.AndFilter;
-import org.apache.druid.segment.filter.IsBooleanFilter;
-import org.apache.druid.segment.filter.OrFilter;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -219,66 +215,6 @@ class ProjectionsTest
Assertions.assertEquals(expected, projectionMatch);
}
- @Test
- void testRewriteFilter()
- {
- Filter xeqfoo = new EqualityFilter("x", ColumnType.STRING, "foo", null);
- Filter xeqfoo2 = new EqualityFilter("x", ColumnType.STRING, "foo", null);
- Filter xeqbar = new EqualityFilter("x", ColumnType.STRING, "bar", null);
- Filter yeqbar = new EqualityFilter("y", ColumnType.STRING, "bar", null);
- Filter zeq123 = new EqualityFilter("z", ColumnType.LONG, 123L, null);
-
- Filter queryFilter = xeqfoo2;
- Assertions.assertInstanceOf(
- ProjectionFilterMatch.class,
- Projections.rewriteFilter(xeqfoo, queryFilter)
- );
-
- queryFilter = yeqbar;
- Assertions.assertNull(Projections.rewriteFilter(xeqfoo, queryFilter));
-
- queryFilter = new AndFilter(List.of(xeqfoo, yeqbar));
- Assertions.assertEquals(
- yeqbar,
- Projections.rewriteFilter(xeqfoo, queryFilter)
- );
-
- queryFilter = new AndFilter(List.of(new OrFilter(List.of(xeqfoo, xeqbar)),
yeqbar));
- Assertions.assertNull(Projections.rewriteFilter(xeqfoo, queryFilter));
-
- queryFilter = new AndFilter(List.of(new IsBooleanFilter(xeqfoo, true),
yeqbar));
- Assertions.assertEquals(yeqbar, Projections.rewriteFilter(xeqfoo,
queryFilter));
-
- queryFilter = new AndFilter(List.of(new IsBooleanFilter(xeqfoo, false),
yeqbar));
- Assertions.assertNull(Projections.rewriteFilter(xeqfoo, queryFilter));
-
- queryFilter = new AndFilter(List.of(new AndFilter(List.of(xeqfoo,
yeqbar)), zeq123));
- Assertions.assertEquals(
- new AndFilter(List.of(yeqbar, zeq123)),
- Projections.rewriteFilter(xeqfoo, queryFilter)
- );
-
- queryFilter = new AndFilter(
- List.of(
- new EqualityFilter("a", ColumnType.STRING, "foo", null),
- new EqualityFilter("b", ColumnType.STRING, "bar", null),
- new EqualityFilter("c", ColumnType.STRING, "baz", null)
- )
- );
- Assertions.assertEquals(
- new EqualityFilter("b", ColumnType.STRING, "bar", null),
- Projections.rewriteFilter(
- new AndFilter(
- List.of(
- new EqualityFilter("a", ColumnType.STRING, "foo", null),
- new EqualityFilter("c", ColumnType.STRING, "baz", null)
- )
- ),
- queryFilter
- )
- );
- }
-
private static class RowSignatureChecker implements
Projections.PhysicalColumnChecker
{
private final RowSignature rowSignature;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]