diff --git a/processing/src/main/java/org/apache/druid/query/policy/Policy.java b/processing/src/main/java/org/apache/druid/query/policy/Policy.java index 11a1aff4fbb8..16d54f753af3 100644 --- a/processing/src/main/java/org/apache/druid/query/policy/Policy.java +++ b/processing/src/main/java/org/apache/druid/query/policy/Policy.java @@ -39,6 +39,11 @@ public interface Policy /** * Apply this policy to a {@link CursorBuildSpec} to seamlessly enforce policies for cursor-based queries. The * application must encapsulate 100% of the requirements of this policy. + *

+ * Any transforms done to the spec must be sure to update {@link CursorBuildSpec#getPhysicalColumns()} and + * {@link CursorBuildSpec#getVirtualColumns()} as needed to ensure the underlying + * {@link org.apache.druid.segment.CursorHolder} that is being restricted has accurate information about the set of + * required columns. */ CursorBuildSpec visit(CursorBuildSpec spec); diff --git a/processing/src/main/java/org/apache/druid/query/policy/RowFilterPolicy.java b/processing/src/main/java/org/apache/druid/query/policy/RowFilterPolicy.java index 97620381344a..a83c89d4268d 100644 --- a/processing/src/main/java/org/apache/druid/query/policy/RowFilterPolicy.java +++ b/processing/src/main/java/org/apache/druid/query/policy/RowFilterPolicy.java @@ -23,12 +23,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Preconditions; import org.apache.druid.query.filter.DimFilter; -import org.apache.druid.query.filter.Filter; import org.apache.druid.segment.CursorBuildSpec; -import org.apache.druid.segment.filter.Filters; import javax.annotation.Nonnull; -import java.util.Arrays; import java.util.Objects; /** @@ -58,12 +55,7 @@ public DimFilter getRowFilter() @Override public CursorBuildSpec visit(CursorBuildSpec spec) { - CursorBuildSpec.CursorBuildSpecBuilder builder = CursorBuildSpec.builder(spec); - final Filter filter = spec.getFilter(); - final Filter policyFilter = this.rowFilter.toFilter(); - - builder.setFilter(Filters.and(Arrays.asList(policyFilter, filter))); - return builder.build(); + return CursorBuildSpec.builder(spec).andFilter(rowFilter.toFilter()).build(); } @Override diff --git a/processing/src/main/java/org/apache/druid/segment/CursorBuildSpec.java b/processing/src/main/java/org/apache/druid/segment/CursorBuildSpec.java index da1cd06b9b00..aca3743cf6c9 100644 --- a/processing/src/main/java/org/apache/druid/segment/CursorBuildSpec.java +++ b/processing/src/main/java/org/apache/druid/segment/CursorBuildSpec.java @@ -20,20 +20,43 @@ package org.apache.druid.segment; import com.google.common.base.Preconditions; +import org.apache.druid.error.DruidException; import org.apache.druid.java.util.common.Intervals; import org.apache.druid.query.OrderBy; import org.apache.druid.query.QueryContext; import org.apache.druid.query.QueryMetrics; import org.apache.druid.query.aggregation.AggregatorFactory; import org.apache.druid.query.filter.Filter; +import org.apache.druid.segment.filter.Filters; +import org.apache.druid.segment.vector.VectorCursor; import org.apache.druid.utils.CollectionUtils; import org.joda.time.Interval; import javax.annotation.Nullable; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; +/** + * Defines the plan for how the reader will scan, filter, transform, group and aggregate, and or order the data from a + * {@link CursorFactory} so that an appropriate {@link CursorHolder} can be constructed. The {@link CursorBuildSpec} + * includes physical and virtual columns will be read from the data, a {@link Filter} so that the {@link Cursor} and + * {@link VectorCursor} only provide matching rows, and details on how the scanned, transformed, and filtered data will + * be grouped, aggegated, and ordered if applicable to allow {@link CursorHolder} construction to provide optimized + * {@link Cursor} or {@link VectorCursor} such as providing cursors for pre-aggregated data with + * {@link org.apache.druid.segment.projections.Projections}. + * + * @see #getFilter() + * @see #getInterval() + * @see #getPhysicalColumns() + * @see #getVirtualColumns() + * @see #getGroupingColumns() + * @see #getAggregators() + * @see #getPreferredOrdering() + */ public class CursorBuildSpec { public static final CursorBuildSpec FULL_SCAN = builder().build(); @@ -114,7 +137,7 @@ public Interval getInterval() /** * Set of physical columns required from a cursor. If null, and {@link #groupingColumns} is null or empty and - * {@link #aggregators} is null or empty, then a {@link CursorHolder} must assume that ALL columns are required + * {@link #aggregators} is null or empty, then a {@link CursorHolder} must assume that ALL columns are required. */ @Nullable public Set getPhysicalColumns() @@ -219,6 +242,42 @@ public boolean isCompatibleOrdering(List ordering) return true; } + @Override + public boolean equals(Object o) + { + if (o == null || getClass() != o.getClass()) { + return false; + } + CursorBuildSpec that = (CursorBuildSpec) o; + return isAggregate == that.isAggregate && + Objects.equals(filter, that.filter) && + Objects.equals(interval, that.interval) && + Objects.equals(groupingColumns, that.groupingColumns) && + Objects.equals(virtualColumns, that.virtualColumns) && + Objects.equals(aggregators, that.aggregators) && + Objects.equals(preferredOrdering, that.preferredOrdering) && + Objects.equals(queryContext, that.queryContext) && + Objects.equals(physicalColumns, that.physicalColumns) && + Objects.equals(queryMetrics, that.queryMetrics); + } + + @Override + public int hashCode() + { + return Objects.hash( + filter, + interval, + groupingColumns, + virtualColumns, + aggregators, + preferredOrdering, + queryContext, + isAggregate, + physicalColumns, + queryMetrics + ); + } + public static class CursorBuildSpecBuilder { @Nullable @@ -235,6 +294,7 @@ public static class CursorBuildSpecBuilder private List preferredOrdering = Collections.emptyList(); private QueryContext queryContext = QueryContext.empty(); + @Nullable private QueryMetrics queryMetrics; @@ -257,7 +317,17 @@ private CursorBuildSpecBuilder(CursorBuildSpec buildSpec) } /** - * @see CursorBuildSpec#getFilter() + * @see CursorBuildSpec#getFilter() for usage. + */ + @Nullable + public Filter getFilter() + { + return filter; + } + + /** + * @see CursorBuildSpec#getFilter() for usage. All {@link Filter#getRequiredColumns()} must be explicitly added to + * {@link #virtualColumns} if virtual or, if set to a non-null value, {@link #physicalColumns}. */ public CursorBuildSpecBuilder setFilter(@Nullable Filter filter) { @@ -266,7 +336,15 @@ public CursorBuildSpecBuilder setFilter(@Nullable Filter filter) } /** - * @see CursorBuildSpec#getInterval() + * @see CursorBuildSpec#getInterval() for usage. + */ + public Interval getInterval() + { + return interval; + } + + /** + * @see CursorBuildSpec#getInterval() for usage. */ public CursorBuildSpecBuilder setInterval(Interval interval) { @@ -275,7 +353,20 @@ public CursorBuildSpecBuilder setInterval(Interval interval) } /** - * @see CursorBuildSpec#getPhysicalColumns() + * @see CursorBuildSpec#getPhysicalColumns() for usage. + */ + @Nullable + public Set getPhysicalColumns() + { + return physicalColumns; + } + + /** + * @see CursorBuildSpec#getPhysicalColumns() for usage. The backing value is not automatically populated by calls to + * {@link #setFilter(Filter)}, {@link #setVirtualColumns(VirtualColumns)}, {@link #setAggregators(List)}, or + * {@link #setPreferredOrdering(List)}, so this must be explicitly set for all required physical columns. If set to + * null, and {@link #groupingColumns} is null or empty and {@link #aggregators} is null or empty, then a + * {@link CursorHolder} must assume that ALL columns are required */ public CursorBuildSpecBuilder setPhysicalColumns(@Nullable Set physicalColumns) { @@ -284,7 +375,16 @@ public CursorBuildSpecBuilder setPhysicalColumns(@Nullable Set physicalC } /** - * @see CursorBuildSpec#getVirtualColumns() + * @see CursorBuildSpec#getVirtualColumns() for usage. All {@link VirtualColumn#requiredColumns()} must be + * explicitly added to {@link #physicalColumns} if it is set to a non-null value. + */ + public VirtualColumns getVirtualColumns() + { + return virtualColumns; + } + + /** + * @see CursorBuildSpec#getVirtualColumns() for usage. */ public CursorBuildSpecBuilder setVirtualColumns(VirtualColumns virtualColumns) { @@ -293,7 +393,16 @@ public CursorBuildSpecBuilder setVirtualColumns(VirtualColumns virtualColumns) } /** - * @see CursorBuildSpec#getGroupingColumns() + * @see CursorBuildSpec#getGroupingColumns() for usage. + */ + @Nullable + public List getGroupingColumns() + { + return groupingColumns; + } + + /** + * @see CursorBuildSpec#getGroupingColumns() for usage. */ public CursorBuildSpecBuilder setGroupingColumns(@Nullable List groupingColumns) { @@ -302,7 +411,17 @@ public CursorBuildSpecBuilder setGroupingColumns(@Nullable List grouping } /** - * @see CursorBuildSpec#getAggregators() + * @see CursorBuildSpec#getAggregators() for usage. + */ + @Nullable + public List getAggregators() + { + return aggregators; + } + + /** + * @see CursorBuildSpec#getAggregators() for usage. All {@link AggregatorFactory#requiredFields()} must be + * explicitly added to {@link #virtualColumns} if virtual or, if set to a non-null value, {@link #physicalColumns}. */ public CursorBuildSpecBuilder setAggregators(@Nullable List aggregators) { @@ -311,7 +430,16 @@ public CursorBuildSpecBuilder setAggregators(@Nullable List a } /** - * @see CursorBuildSpec#getPreferredOrdering() + * @see CursorBuildSpec#getPreferredOrdering() for usage. + */ + public List getPreferredOrdering() + { + return preferredOrdering; + } + + /** + * @see CursorBuildSpec#getPreferredOrdering() for usage. All {@link OrderBy#getColumnName()} must be explicitly + * added to {@link #virtualColumns} if virtual or, if set to a non-null value, {@link #physicalColumns}. */ public CursorBuildSpecBuilder setPreferredOrdering(List preferredOrdering) { @@ -319,6 +447,14 @@ public CursorBuildSpecBuilder setPreferredOrdering(List preferredOrderi return this; } + /** + * @see CursorBuildSpec#getQueryContext() + */ + public QueryContext getQueryContext() + { + return queryContext; + } + /** * @see CursorBuildSpec#getQueryContext() */ @@ -328,6 +464,15 @@ public CursorBuildSpecBuilder setQueryContext(QueryContext queryContext) return this; } + /** + * @see CursorBuildSpec#getQueryMetrics() + */ + @Nullable + public QueryMetrics getQueryMetrics() + { + return queryMetrics; + } + /** * @see CursorBuildSpec#getQueryMetrics() */ @@ -337,6 +482,39 @@ public CursorBuildSpecBuilder setQueryMetrics(@Nullable QueryMetrics queryMet return this; } + + + /** + * Adds a {@link Filter} to the builder, if {@link #filter} is already set, the existing and new filters will be + * combined with an {@link org.apache.druid.segment.filter.AndFilter}. If {@link #physicalColumns} is set, + * {@link Filter#getRequiredColumns()} which are not present in {@link #virtualColumns} will be added to the + * existing set of {@link #physicalColumns}. + */ + public CursorBuildSpecBuilder andFilter( + Filter filterToAdd + ) + { + DruidException.conditionalDefensive(filterToAdd != null, "filterToAdd must not be null"); + final Filter newFilter; + final Set newPhysicalColumns; + if (filter == null) { + newFilter = filterToAdd; + } else { + newFilter = Filters.and(Arrays.asList(filter, filterToAdd)); + } + if (physicalColumns != null) { + newPhysicalColumns = new HashSet<>(physicalColumns); + for (String column : filterToAdd.getRequiredColumns()) { + if (!virtualColumns.exists(column)) { + newPhysicalColumns.add(column); + } + } + } else { + newPhysicalColumns = null; + } + return setFilter(newFilter).setPhysicalColumns(newPhysicalColumns); + } + public CursorBuildSpec build() { return new CursorBuildSpec( diff --git a/processing/src/main/java/org/apache/druid/segment/CursorFactory.java b/processing/src/main/java/org/apache/druid/segment/CursorFactory.java index b2547ebe38c0..2effd1b5fa2f 100644 --- a/processing/src/main/java/org/apache/druid/segment/CursorFactory.java +++ b/processing/src/main/java/org/apache/druid/segment/CursorFactory.java @@ -26,6 +26,10 @@ public interface CursorFactory extends ColumnInspector { + /** + * Creates a {@link CursorHolder} for a given {@link CursorBuildSpec} which describes how the reader is going to + * scan, filter, transform, group and aggregate, and/or order the data. + */ CursorHolder makeCursorHolder(CursorBuildSpec spec); /** diff --git a/processing/src/main/java/org/apache/druid/segment/CursorHolder.java b/processing/src/main/java/org/apache/druid/segment/CursorHolder.java index 808e1eb53846..3ce8d813c6c3 100644 --- a/processing/src/main/java/org/apache/druid/segment/CursorHolder.java +++ b/processing/src/main/java/org/apache/druid/segment/CursorHolder.java @@ -31,6 +31,22 @@ import java.util.Collections; import java.util.List; +/** + * Provides {@link Cursor} and if available, {@link VectorCursor} which readers can use to scan a set of rows defined + * originally from a {@link CursorBuildSpec} which describes how data is to be scanned, transformed, filter, grouped and + * aggregated, and/or ordered. + *

+ * If {@link #canVectorize()} then {@link #asVectorCursor()} will return a non-null value allowing for processing rows + * in batches instead of individually. + *

+ * If {@link #isPreAggregated()} is true, readers which are aggregating must call + * {@link #getAggregatorsForPreAggregated()} to get the updated set of {@link AggregatorFactory} to correctly process + * results + *

+ * {@link #getOrdering()} defines how the data is ordered in the {@link Cursor} or {@link VectorCursor}, allowing + * readers to compare to their own desired ordering and potentially skip ordering their own results if it is + * pre-ordered. + */ public interface CursorHolder extends Closeable { /** @@ -60,8 +76,9 @@ default boolean canVectorize() } /** - * Returns true if the {@link Cursor} or {@link VectorCursor} contains pre-aggregated columns for all - * {@link AggregatorFactory} specified in {@link CursorBuildSpec#getAggregators()}. + * Returns true if the {@link Cursor} or {@link VectorCursor} contains pre-aggregated columns for all grouping columns + * specified in {@link CursorBuildSpec#getGroupingColumns()} and all {@link AggregatorFactory} specified in + * {@link CursorBuildSpec#getAggregators()}. *

* If this method returns true, {@link ColumnSelectorFactory} and * {@link org.apache.druid.segment.vector.VectorColumnSelectorFactory} created from {@link Cursor} and @@ -90,7 +107,7 @@ default List getAggregatorsForPreAggregated() /** * Returns cursor ordering, which may or may not match {@link CursorBuildSpec#getPreferredOrdering()}. If returns * an empty list then the cursor has no defined ordering. - * + *

* Cursors associated with this holder return rows in this ordering, using the natural comparator for the column type. * Includes {@link ColumnHolder#TIME_COLUMN_NAME} if appropriate. */ diff --git a/processing/src/main/java/org/apache/druid/segment/FilteredCursorFactory.java b/processing/src/main/java/org/apache/druid/segment/FilteredCursorFactory.java index 5a120f7ebb8b..007543f76019 100644 --- a/processing/src/main/java/org/apache/druid/segment/FilteredCursorFactory.java +++ b/processing/src/main/java/org/apache/druid/segment/FilteredCursorFactory.java @@ -20,15 +20,10 @@ package org.apache.druid.segment; import org.apache.druid.query.filter.DimFilter; -import org.apache.druid.query.filter.Filter; import org.apache.druid.segment.column.ColumnCapabilities; import org.apache.druid.segment.column.RowSignature; -import org.apache.druid.segment.filter.Filters; import javax.annotation.Nullable; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; public class FilteredCursorFactory implements CursorFactory { @@ -45,32 +40,10 @@ public FilteredCursorFactory(CursorFactory delegate, @Nullable DimFilter filter) @Override public CursorHolder makeCursorHolder(CursorBuildSpec spec) { - final CursorBuildSpec.CursorBuildSpecBuilder buildSpecBuilder = CursorBuildSpec.builder(spec); - final Filter newFilter; - final Set physicalColumns; - if (filter != null) { - if (spec.getFilter() == null) { - newFilter = filter.toFilter(); - } else { - newFilter = Filters.and(Arrays.asList(spec.getFilter(), filter.toFilter())); - } - if (spec.getPhysicalColumns() != null) { - physicalColumns = new HashSet<>(spec.getPhysicalColumns()); - for (String column : filter.getRequiredColumns()) { - if (!spec.getVirtualColumns().exists(column)) { - physicalColumns.add(column); - } - } - } else { - physicalColumns = null; - } - } else { - newFilter = spec.getFilter(); - physicalColumns = spec.getPhysicalColumns(); + if (filter == null) { + return delegate.makeCursorHolder(spec); } - buildSpecBuilder.setFilter(newFilter) - .setPhysicalColumns(physicalColumns); - return delegate.makeCursorHolder(buildSpecBuilder.build()); + return delegate.makeCursorHolder(CursorBuildSpec.builder(spec).andFilter(filter.toFilter()).build()); } @Override diff --git a/processing/src/test/java/org/apache/druid/query/policy/RowFilterPolicyTest.java b/processing/src/test/java/org/apache/druid/query/policy/RowFilterPolicyTest.java index 193843dbfcad..265b7322092a 100644 --- a/processing/src/test/java/org/apache/druid/query/policy/RowFilterPolicyTest.java +++ b/processing/src/test/java/org/apache/druid/query/policy/RowFilterPolicyTest.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import nl.jqno.equalsverifier.EqualsVerifier; import org.apache.druid.query.filter.DimFilter; import org.apache.druid.query.filter.EqualityFilter; @@ -82,12 +83,17 @@ public void testVisit() public void testVisit_combineFilters() { Filter filter = new EqualityFilter("col0", ColumnType.STRING, "val0", null); - CursorBuildSpec spec = CursorBuildSpec.builder().setFilter(filter).build(); + CursorBuildSpec spec = CursorBuildSpec.builder() + .setFilter(filter) + .setPhysicalColumns(filter.getRequiredColumns()) + .build(); DimFilter policyFilter = new EqualityFilter("col", ColumnType.STRING, "val", null); final RowFilterPolicy policy = RowFilterPolicy.from(policyFilter); Filter expected = new AndFilter(ImmutableList.of(policyFilter.toFilter(), filter)); - Assert.assertEquals(expected, policy.visit(spec).getFilter()); + final CursorBuildSpec transformed = policy.visit(spec); + Assert.assertEquals(expected, transformed.getFilter()); + Assert.assertEquals(ImmutableSet.of("col", "col0"), transformed.getPhysicalColumns()); } } diff --git a/processing/src/test/java/org/apache/druid/segment/CursorBuildSpecTest.java b/processing/src/test/java/org/apache/druid/segment/CursorBuildSpecTest.java new file mode 100644 index 000000000000..7e19858c5a34 --- /dev/null +++ b/processing/src/test/java/org/apache/druid/segment/CursorBuildSpecTest.java @@ -0,0 +1,295 @@ +/* + * 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; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import nl.jqno.equalsverifier.EqualsVerifier; +import org.apache.druid.error.DruidException; +import org.apache.druid.query.OrderBy; +import org.apache.druid.query.expression.TestExprMacroTable; +import org.apache.druid.query.filter.EqualityFilter; +import org.apache.druid.query.filter.RangeFilter; +import org.apache.druid.segment.column.ColumnHolder; +import org.apache.druid.segment.column.ColumnType; +import org.apache.druid.segment.filter.AndFilter; +import org.apache.druid.segment.virtual.ExpressionVirtualColumn; +import org.junit.Assert; +import org.junit.Test; + +public class CursorBuildSpecTest +{ + @Test + public void testEquals() + { + EqualsVerifier.forClass(CursorBuildSpec.class).usingGetClass().verify(); + } + + @Test + public void testIsCompatibleOrdering() + { + // test specified preferred ordering by the query + CursorBuildSpec spec1 = CursorBuildSpec.builder() + .setPhysicalColumns(ImmutableSet.of(ColumnHolder.TIME_COLUMN_NAME, "x", "y")) + .setPreferredOrdering( + ImmutableList.of( + OrderBy.ascending(ColumnHolder.TIME_COLUMN_NAME), + OrderBy.ascending("x") + ) + ) + .build(); + // fail if cursor isn't fully ordered by the preferred ordering of the spec + Assert.assertFalse(spec1.isCompatibleOrdering(ImmutableList.of(OrderBy.ascending(ColumnHolder.TIME_COLUMN_NAME)))); + // pass if the cursor ordering exactly matches + Assert.assertTrue( + spec1.isCompatibleOrdering( + ImmutableList.of(OrderBy.ascending(ColumnHolder.TIME_COLUMN_NAME), OrderBy.ascending("x")) + ) + ); + // pass if the cursor ordering includes additional ordering not specified by the spec preferred ordering + Assert.assertTrue( + spec1.isCompatibleOrdering( + ImmutableList.of( + OrderBy.ascending(ColumnHolder.TIME_COLUMN_NAME), + OrderBy.ascending("x"), + OrderBy.descending("y") + ) + ) + ); + // fail if the cursor ordering is different + Assert.assertFalse( + spec1.isCompatibleOrdering( + ImmutableList.of( + OrderBy.ascending(ColumnHolder.TIME_COLUMN_NAME), + OrderBy.descending("y"), + OrderBy.ascending("x") + ) + ) + ); + + // test no specified preferred ordering by the reader + CursorBuildSpec spec2 = CursorBuildSpec.builder() + .setPhysicalColumns(ImmutableSet.of(ColumnHolder.TIME_COLUMN_NAME, "x", "y")) + .build(); + Assert.assertTrue( + spec2.isCompatibleOrdering( + ImmutableList.of(OrderBy.ascending(ColumnHolder.TIME_COLUMN_NAME), OrderBy.ascending("x")) + ) + ); + } + + @Test + public void testBuilderAndFilterNoExistingFilterWithPhysicalColumns() + { + CursorBuildSpec.CursorBuildSpecBuilder builder = + CursorBuildSpec.builder() + .setPhysicalColumns(ImmutableSet.of("x", "y")) + .setVirtualColumns( + VirtualColumns.create( + new ExpressionVirtualColumn( + "v0", + "y + 2", + ColumnType.LONG, + TestExprMacroTable.INSTANCE + ) + ) + ); + + builder.andFilter(new EqualityFilter("z", ColumnType.STRING, "hello", null)); + + Assert.assertEquals(new EqualityFilter("z", ColumnType.STRING, "hello", null), builder.getFilter()); + Assert.assertEquals( + ImmutableSet.of("x", "y", "z"), + builder.getPhysicalColumns() + ); + } + + @Test + public void testBuilderAndFilterNoExistingFilterWithPhysicalColumnsNoNewReferences() + { + CursorBuildSpec.CursorBuildSpecBuilder builder = + CursorBuildSpec.builder() + .setPhysicalColumns(ImmutableSet.of("x", "y")) + .setVirtualColumns( + VirtualColumns.create( + new ExpressionVirtualColumn( + "v0", + "y + 2", + ColumnType.LONG, + TestExprMacroTable.INSTANCE + ) + ) + ); + + builder.andFilter(new EqualityFilter("x", ColumnType.STRING, "hello", null)); + + Assert.assertEquals(new EqualityFilter("x", ColumnType.STRING, "hello", null), builder.getFilter()); + Assert.assertEquals( + ImmutableSet.of("x", "y"), + builder.getPhysicalColumns() + ); + } + + @Test + public void testBuilderAndFilterExistingFilterWithPhysicalColumns() + { + CursorBuildSpec.CursorBuildSpecBuilder builder = + CursorBuildSpec.builder() + .setFilter(new EqualityFilter("x", ColumnType.STRING, "foo", null)) + .setPhysicalColumns(ImmutableSet.of("x", "y")) + .setVirtualColumns( + VirtualColumns.create( + new ExpressionVirtualColumn( + "v0", + "y + 2", + ColumnType.LONG, + TestExprMacroTable.INSTANCE + ) + ) + ); + + builder.andFilter(new EqualityFilter("z", ColumnType.STRING, "hello", null)); + + Assert.assertEquals( + new AndFilter( + ImmutableList.of( + new EqualityFilter("x", ColumnType.STRING, "foo", null), + new EqualityFilter("z", ColumnType.STRING, "hello", null) + ) + ), + builder.getFilter() + ); + Assert.assertEquals( + ImmutableSet.of("x", "y", "z"), + builder.getPhysicalColumns() + ); + } + + @Test + public void testBuilderAndFilterNoExistingFilterNoPhysicalColumns() + { + CursorBuildSpec.CursorBuildSpecBuilder builder = + CursorBuildSpec.builder(); + + builder.andFilter(new EqualityFilter("z", ColumnType.STRING, "hello", null)); + + Assert.assertEquals(new EqualityFilter("z", ColumnType.STRING, "hello", null), builder.getFilter()); + Assert.assertNull( + builder.getPhysicalColumns() + ); + } + + @Test + public void testBuilderAndFilterExistingFilterNoPhysicalColumns() + { + CursorBuildSpec.CursorBuildSpecBuilder builder = + CursorBuildSpec.builder().setFilter(new EqualityFilter("x", ColumnType.STRING, "foo", null)); + + builder.andFilter(new EqualityFilter("z", ColumnType.STRING, "hello", null)); + + Assert.assertEquals( + new AndFilter( + ImmutableList.of( + new EqualityFilter("x", ColumnType.STRING, "foo", null), + new EqualityFilter("z", ColumnType.STRING, "hello", null) + ) + ), + builder.getFilter() + ); + Assert.assertNull( + builder.getPhysicalColumns() + ); + } + + @Test + public void testBuilderAndFilterNoExistingFilterUsingVirtualColumn() + { + CursorBuildSpec.CursorBuildSpecBuilder builder = + CursorBuildSpec.builder() + .setPhysicalColumns(ImmutableSet.of("x", "y")) + .setVirtualColumns( + VirtualColumns.create( + new ExpressionVirtualColumn( + "v0", + "y + 2", + ColumnType.LONG, + TestExprMacroTable.INSTANCE + ) + ) + ); + builder.andFilter(new RangeFilter("v0", ColumnType.LONG, 1L, 3L, true, true, null)); + + Assert.assertEquals( + new RangeFilter("v0", ColumnType.LONG, 1L, 3L, true, true, null), + builder.getFilter() + ); + Assert.assertEquals( + ImmutableSet.of("x", "y"), + builder.getPhysicalColumns() + ); + } + + @Test + public void testBuilderAndFilterExistingFilterNewFilterUsingVirtualColumn() + { + CursorBuildSpec.CursorBuildSpecBuilder builder = + CursorBuildSpec.builder() + .setFilter(new EqualityFilter("x", ColumnType.STRING, "foo", null)) + .setPhysicalColumns(ImmutableSet.of("x", "y")) + .setVirtualColumns( + VirtualColumns.create( + new ExpressionVirtualColumn( + "v0", + "y + 2", + ColumnType.LONG, + TestExprMacroTable.INSTANCE + ) + ) + ); + builder.andFilter(new RangeFilter("v0", ColumnType.LONG, 1L, 3L, true, true, null)); + + Assert.assertEquals( + new AndFilter( + ImmutableList.of( + new EqualityFilter("x", ColumnType.STRING, "foo", null), + new RangeFilter("v0", ColumnType.LONG, 1L, 3L, true, true, null) + ) + ), + builder.getFilter() + ); + Assert.assertEquals( + ImmutableSet.of("x", "y"), + builder.getPhysicalColumns() + ); + } + + @Test + public void testAndFilterNull() + { + CursorBuildSpec.CursorBuildSpecBuilder builder = CursorBuildSpec.builder(); + + Throwable t = Assert.assertThrows( + DruidException.class, + () -> builder.andFilter(null) + ); + + Assert.assertEquals("filterToAdd must not be null", t.getMessage()); + } +}