Browse Source

SONAR-13204 support filters on nested field in RequestFiltersComputer

tags/8.3.0.34182
Jacek 4 years ago
parent
commit
50a9f8a09d
16 changed files with 1066 additions and 330 deletions
  1. 61
    0
      server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/NestedFieldTopAggregationDefinition.java
  2. 40
    49
      server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/RequestFiltersComputer.java
  3. 10
    7
      server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/SimpleFieldTopAggregationDefinition.java
  4. 4
    4
      server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/SubAggregationHelper.java
  5. 136
    4
      server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/TopAggregationDefinition.java
  6. 4
    3
      server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/TopAggregationHelper.java
  7. 14
    24
      server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/AllFiltersTest.java
  8. 150
    0
      server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/NestedFieldFilterScopeTest.java
  9. 140
    0
      server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/NestedFieldTopAggregationDefinitionTest.java
  10. 289
    59
      server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/RequestFiltersComputerTest.java
  11. 92
    0
      server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/SimpleFieldFilterScopeTest.java
  12. 24
    5
      server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/SimpleFieldTopAggregationDefinitionTest.java
  13. 13
    13
      server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/SubAggregationHelperTest.java
  14. 0
    89
      server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/TermTopAggregationDefTest.java
  15. 17
    17
      server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/TopAggregationHelperTest.java
  16. 72
    56
      server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java

+ 61
- 0
server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/NestedFieldTopAggregationDefinition.java View File

@@ -0,0 +1,61 @@
/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.es.searchrequest;

import java.util.Arrays;
import java.util.Objects;
import javax.annotation.concurrent.Immutable;
import org.apache.commons.lang.StringUtils;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.NestedFieldFilterScope;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;

@Immutable
public class NestedFieldTopAggregationDefinition<T> implements TopAggregationDefinition<NestedFieldFilterScope<T>> {
private final NestedFieldFilterScope<T> filterScope;
private final boolean sticky;

public NestedFieldTopAggregationDefinition(String nestedFieldPath, T value, boolean sticky) {
requireNonNull(nestedFieldPath, "nestedFieldPath can't be null");
requireNonNull(value, "value can't be null");
checkArgument(StringUtils.countMatches(nestedFieldPath, ".") == 1,
"Field path should have only one dot: %s", nestedFieldPath);
String[] fullPath = Arrays.stream(StringUtils.split(nestedFieldPath, '.'))
.filter(Objects::nonNull)
.map(String::trim)
.filter(t -> !t.isEmpty())
.toArray(String[]::new);
checkArgument(fullPath.length == 2,
"field path \"%s\" should have exactly 2 non empty field names, got: %s", nestedFieldPath, Arrays.asList(fullPath));
this.filterScope = new NestedFieldFilterScope<>(fullPath[0], fullPath[1], value);
this.sticky = sticky;
}

@Override
public NestedFieldFilterScope<T> getFilterScope() {
return filterScope;
}

@Override
public boolean isSticky() {
return sticky;
}
}

+ 40
- 49
server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/RequestFiltersComputer.java View File

@@ -34,6 +34,7 @@ import javax.annotation.concurrent.Immutable;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.FilterScope;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
@@ -52,15 +53,15 @@ import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
* </ul>
* <p>
* To be able to provide accurate filters, all {@link TopAggregationDefinition} instances for which
* {@link #getTopAggregationFilter(TopAggregationDefinition)} may be called, must be declared in the constructor.
* {@link #getTopAggregationFilter(TopAggregationDefinition)} may be called, must all be declared in the constructor.
*/
public class RequestFiltersComputer {

private final Set<TopAggregationDefinition> topAggregations;
private final Map<FilterNameAndFieldName, QueryBuilder> postFilters;
private final Map<FilterNameAndFieldName, QueryBuilder> queryFilters;
private final Set<TopAggregationDefinition<?>> topAggregations;
private final Map<FilterNameAndScope, QueryBuilder> postFilters;
private final Map<FilterNameAndScope, QueryBuilder> queryFilters;

public RequestFiltersComputer(AllFilters allFilters, Set<TopAggregationDefinition> topAggregations) {
public RequestFiltersComputer(AllFilters allFilters, Set<TopAggregationDefinition<?>> topAggregations) {
this.topAggregations = ImmutableSet.copyOf(topAggregations);
this.postFilters = computePostFilters((AllFiltersImpl) allFilters, topAggregations);
this.queryFilters = computeQueryFilter((AllFiltersImpl) allFilters, postFilters);
@@ -73,20 +74,20 @@ public class RequestFiltersComputer {
/**
* Any filter of the query which can not be applied to all top-aggregations must be applied as a PostFilter.
* <p>
* A filter applying to some field can not be applied to the query when at least one sticky top-aggregation is enabled
* which applies to that field <strong>and</strong> a top-aggregation is enabled on that field.
* A filter with a given {@link FilterScope} can not be applied to the query when at least one sticky top-aggregation
* is enabled which has the same {@link FilterScope}.
*/
private static Map<FilterNameAndFieldName, QueryBuilder> computePostFilters(AllFiltersImpl allFilters,
Set<TopAggregationDefinition> topAggregations) {
Set<String> enabledStickyTopAggregationtedFieldNames = topAggregations.stream()
private static Map<FilterNameAndScope, QueryBuilder> computePostFilters(AllFiltersImpl allFilters,
Set<TopAggregationDefinition<?>> topAggregations) {
Set<FilterScope> enabledStickyTopAggregationtedFieldNames = topAggregations.stream()
.filter(TopAggregationDefinition::isSticky)
.map(TopAggregationDefinition::getFieldName)
.map(TopAggregationDefinition::getFilterScope)
.collect(MoreCollectors.toSet(topAggregations.size()));

// use LinkedHashMap over MoreCollectors.uniqueIndex to preserve order and write UTs more easily
Map<FilterNameAndFieldName, QueryBuilder> res = new LinkedHashMap<>();
Map<FilterNameAndScope, QueryBuilder> res = new LinkedHashMap<>();
allFilters.internalStream()
.filter(e -> enabledStickyTopAggregationtedFieldNames.contains(e.getKey().getFieldName()))
.filter(e -> enabledStickyTopAggregationtedFieldNames.contains(e.getKey().getFilterScope()))
.forEach(e -> checkState(res.put(e.getKey(), e.getValue()) == null, "Duplicate: %s", e.getKey()));
return res;
}
@@ -99,12 +100,12 @@ public class RequestFiltersComputer {
* (typical case is a filter on the field aggregated to implement sticky facet behavior), this filter can
* not be applied to the query and therefor must be applied as PostFilter.
*/
private static Map<FilterNameAndFieldName, QueryBuilder> computeQueryFilter(AllFiltersImpl allFilters,
Map<FilterNameAndFieldName, QueryBuilder> postFilters) {
Set<FilterNameAndFieldName> postFilterKeys = postFilters.keySet();
private static Map<FilterNameAndScope, QueryBuilder> computeQueryFilter(AllFiltersImpl allFilters,
Map<FilterNameAndScope, QueryBuilder> postFilters) {
Set<FilterNameAndScope> postFilterKeys = postFilters.keySet();

// use LinkedHashMap over MoreCollectors.uniqueIndex to preserve order and write UTs more easily
Map<FilterNameAndFieldName, QueryBuilder> res = new LinkedHashMap<>();
Map<FilterNameAndScope, QueryBuilder> res = new LinkedHashMap<>();
allFilters.internalStream()
.filter(e -> !postFilterKeys.contains(e.getKey()))
.forEach(e -> checkState(res.put(e.getKey(), e.getValue()) == null, "Duplicate: %s", e.getKey()));
@@ -133,26 +134,27 @@ public class RequestFiltersComputer {
}

/**
* The {@link BoolQueryBuilder} to apply to the top aggregation for the specified {@link TopAggregationDef}.
* The {@link BoolQueryBuilder} to apply to the top aggregation for the specified {@link SimpleFieldTopAggregationDefinition}.
* <p>
* The filter of the aggregations for a top-aggregation will either be:
* <ul>
* <li>the same as PostFilter, if the top-aggregation is non-sticky or the field the top-aggregation applies
* to is not being filtered</li>
* <li>or the same as PostFilter minus any filter which applies to the field for the top-aggregation (if it's sticky)</li>
* <li>the same as PostFilter, if the top-aggregation is non-sticky or none have the same FilterScope as
* {@code topAggregation}</li>
* <li>or the same as PostFilter minus any filter which applies to the same {@link FilterScope} of
* {@code topAggregation}for the (<strong>if it's sticky</strong>)</li>
* </ul>
*
* @throws IllegalArgumentException if specified {@link TopAggregationDefinition} has not been specified in the constructor
*/
public Optional<BoolQueryBuilder> getTopAggregationFilter(TopAggregationDefinition topAggregation) {
public Optional<BoolQueryBuilder> getTopAggregationFilter(TopAggregationDefinition<?> topAggregation) {
checkArgument(topAggregations.contains(topAggregation), "topAggregation must have been declared in constructor");
return toBoolQuery(
postFilters,
(e, v) -> !topAggregation.isSticky() || !topAggregation.getFieldName().equals(e.getFieldName()));
(e, v) -> !topAggregation.isSticky() || !topAggregation.getFilterScope().intersect(e.getFilterScope()));
}

private static Optional<BoolQueryBuilder> toBoolQuery(Map<FilterNameAndFieldName, QueryBuilder> queryFilters,
BiPredicate<FilterNameAndFieldName, QueryBuilder> predicate) {
private static Optional<BoolQueryBuilder> toBoolQuery(Map<FilterNameAndScope, QueryBuilder> queryFilters,
BiPredicate<FilterNameAndScope, QueryBuilder> predicate) {
if (queryFilters.isEmpty()) {
return empty();
}
@@ -178,13 +180,7 @@ public class RequestFiltersComputer {
/**
* @throws IllegalArgumentException if a filter with the specified name has already been added
*/
AllFilters addFilter(String name, String fieldName, @Nullable QueryBuilder filter);

/**
* Convenience method for usage of {@link #addFilter(String, String, QueryBuilder)} when name of the filter is
* the same as the field name.
*/
AllFilters addFilter(String fieldName, @Nullable QueryBuilder filter);
AllFilters addFilter(String name, FilterScope filterScope, @Nullable QueryBuilder filter);

Stream<QueryBuilder> stream();
}
@@ -194,24 +190,19 @@ public class RequestFiltersComputer {
* Usage of LinkedHashMap only benefits unit tests by providing predictability of the order of the filters.
* ES doesn't care of the order.
*/
private final Map<FilterNameAndFieldName, QueryBuilder> filters = new LinkedHashMap<>();

@Override
public AllFilters addFilter(String fieldName, @Nullable QueryBuilder filter) {
return addFilter(fieldName, fieldName, filter);
}
private final Map<FilterNameAndScope, QueryBuilder> filters = new LinkedHashMap<>();

@Override
public AllFilters addFilter(String name, String fieldName, @Nullable QueryBuilder filter) {
public AllFilters addFilter(String name, FilterScope filterScope, @Nullable QueryBuilder filter) {
requireNonNull(name, "name can't be null");
requireNonNull(fieldName, "fieldName can't be null");
requireNonNull(filterScope, "filterScope can't be null");

if (filter == null) {
return this;
}

checkArgument(
filters.put(new FilterNameAndFieldName(name, fieldName), filter) == null,
filters.put(new FilterNameAndScope(name, filterScope), filter) == null,
"A filter with name %s has already been added", name);
return this;
}
@@ -221,7 +212,7 @@ public class RequestFiltersComputer {
return filters.values().stream();
}

private Stream<Map.Entry<FilterNameAndFieldName, QueryBuilder>> internalStream() {
private Stream<Map.Entry<FilterNameAndScope, QueryBuilder>> internalStream() {
return filters.entrySet().stream();
}
}
@@ -233,17 +224,17 @@ public class RequestFiltersComputer {
* This saves from using two internal maps.
*/
@Immutable
private static final class FilterNameAndFieldName {
private static final class FilterNameAndScope {
private final String filterName;
private final String fieldName;
private final FilterScope filterScope;

public FilterNameAndFieldName(String filterName, String fieldName) {
private FilterNameAndScope(String filterName, FilterScope filterScope) {
this.filterName = filterName;
this.fieldName = fieldName;
this.filterScope = filterScope;
}

public String getFieldName() {
return fieldName;
public FilterScope getFilterScope() {
return filterScope;
}

@Override
@@ -254,7 +245,7 @@ public class RequestFiltersComputer {
if (o == null || getClass() != o.getClass()) {
return false;
}
FilterNameAndFieldName that = (FilterNameAndFieldName) o;
FilterNameAndScope that = (FilterNameAndScope) o;
return filterName.equals(that.filterName);
}


server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/TopAggregationDef.java → server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/SimpleFieldTopAggregationDefinition.java View File

@@ -20,29 +20,32 @@
package org.sonar.server.es.searchrequest;

import javax.annotation.concurrent.Immutable;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.FilterScope;

import static java.util.Objects.requireNonNull;

/**
* Default implementation of {@link TopAggregationDefinition}.
* Implementation of {@link TopAggregationDefinition} with a filter scope for a simple field.
*/
@Immutable
public final class TopAggregationDef implements TopAggregationDefinition {
private final String fieldName;
public final class SimpleFieldTopAggregationDefinition implements TopAggregationDefinition<FilterScope> {
private final FilterScope filterScope;
private final boolean sticky;

public TopAggregationDef(String fieldName, boolean sticky) {
this.fieldName = requireNonNull(fieldName, "fieldName can't be null");
public SimpleFieldTopAggregationDefinition(String fieldName, boolean sticky) {
requireNonNull(fieldName, "fieldName can't be null");
this.filterScope = new SimpleFieldFilterScope(fieldName);
this.sticky = sticky;
}

@Override
public String getFieldName() {
return fieldName;
public FilterScope getFilterScope() {
return filterScope;
}

@Override
public boolean isSticky() {
return sticky;
}

}

+ 4
- 4
server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/SubAggregationHelper.java View File

@@ -63,9 +63,9 @@ public class SubAggregationHelper {
}

public TermsAggregationBuilder buildTermsAggregation(String name,
TopAggregationDefinition topAggregation, @Nullable Integer numberOfTerms) {
TopAggregationDefinition<?> topAggregation, @Nullable Integer numberOfTerms) {
TermsAggregationBuilder termsAggregation = AggregationBuilders.terms(name)
.field(topAggregation.getFieldName())
.field(topAggregation.getFilterScope().getFieldName())
.order(order)
.minDocCount(TERM_AGGREGATION_MIN_DOC_COUNT);
if (numberOfTerms != null) {
@@ -77,7 +77,7 @@ public class SubAggregationHelper {
return termsAggregation;
}

public <T> Optional<TermsAggregationBuilder> buildSelectedItemsAggregation(String name, TopAggregationDefinition topAggregation, T[] selected) {
public <T> Optional<TermsAggregationBuilder> buildSelectedItemsAggregation(String name, TopAggregationDefinition<?> topAggregation, T[] selected) {
if (selected.length <= 0) {
return Optional.empty();
}
@@ -89,7 +89,7 @@ public class SubAggregationHelper {

TermsAggregationBuilder selectedTerms = AggregationBuilders.terms(name + Facets.SELECTED_SUB_AGG_NAME_SUFFIX)
.size(max(MAXIMUM_NUMBER_OF_SELECTED_ITEMS_WHOSE_DOC_COUNT_WILL_BE_CALCULATED, includes.length()))
.field(topAggregation.getFieldName())
.field(topAggregation.getFilterScope().getFieldName())
.includeExclude(new IncludeExclude(includes, null));
if (subAggregation != null) {
selectedTerms = selectedTerms.subAggregation(subAggregation);

+ 136
- 4
server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/TopAggregationDefinition.java View File

@@ -19,12 +19,144 @@
*/
package org.sonar.server.es.searchrequest;

import java.util.Objects;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import static java.util.Objects.requireNonNull;

/**
* Models a first level aggregation in an Elasticsearch request (aka. top-aggregation) on a unique field and whether
* it is to be used to compute data for a sticky facet (see {@link #isSticky()}).
* Models a first level aggregation in an Elasticsearch request (aka. top-aggregation) with a specific
* {@link FilterScope} and whether it is to be used to compute data for a sticky facet (see {@link #isSticky()}) or not.
*/
public interface TopAggregationDefinition {
String getFieldName();
public interface TopAggregationDefinition<S extends TopAggregationDefinition.FilterScope> {
boolean STICKY = true;
boolean NON_STICKY = false;

S getFilterScope();

boolean isSticky();

abstract class FilterScope {
public abstract String getFieldName();

/**
* All implementations must implement {@link Object#equals(Object)} and {@link Object#hashCode()} to be used
* in {@link java.util.Set}.
*/
public abstract boolean equals(Object other);

public abstract int hashCode();

/**
*
*/
public abstract boolean intersect(@Nullable FilterScope other);
}

/**
* Filter applies to a regular first level field.
*/
@Immutable
final class SimpleFieldFilterScope extends FilterScope {
private final String fieldName;

public SimpleFieldFilterScope(String fieldName) {
this.fieldName = requireNonNull(fieldName, "fieldName can't be null");
}

@Override
public String getFieldName() {
return fieldName;
}

@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SimpleFieldFilterScope that = (SimpleFieldFilterScope) o;
return fieldName.equals(that.fieldName);
}

@Override
public int hashCode() {
return Objects.hash(fieldName);
}

@Override
public boolean intersect(@Nullable FilterScope other) {
if (this == other) {
return true;
}
if (other == null) {
return false;
}
return fieldName.equals(other.getFieldName());
}
}

/**
* Filter applies to a first level field holding an array of objects (aka. nested fields) used to store various data
* with similar structure in a single field. Each data is identified by a dedicated field which holds a different value
* for each data: eg. metric key subfield identifies which metric the other subfield(s) are the value of.
* <p>
* This filter scope allows to represent filtering on each type of data in this first-level field, hence the necessity
* to provide both the name of the subfield and the value which identifies that data.
*/
@Immutable
final class NestedFieldFilterScope<T> extends FilterScope {
private final String fieldName;
private final String nestedFieldName;
private final T value;

public NestedFieldFilterScope(String fieldName, String nestedFieldName, T value) {
this.fieldName = requireNonNull(fieldName, "fieldName can't be null");
this.nestedFieldName = requireNonNull(nestedFieldName, "nestedFieldName can't be null");
this.value = requireNonNull(value, "value can't be null");
}

@Override
public String getFieldName() {
return fieldName;
}

public String getNestedFieldName() {
return nestedFieldName;
}

public T getNestedFieldValue() {
return value;
}

@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NestedFieldFilterScope<?> that = (NestedFieldFilterScope<?>) o;
return fieldName.equals(that.fieldName) &&
nestedFieldName.equals(that.nestedFieldName) &&
value.equals(that.value);
}

@Override
public int hashCode() {
return Objects.hash(fieldName, nestedFieldName, value);
}

@Override
public boolean intersect(@Nullable FilterScope other) {
if (other instanceof NestedFieldFilterScope) {
return equals(other);
}
return false;
}
}
}

+ 4
- 3
server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/TopAggregationHelper.java View File

@@ -62,7 +62,7 @@ public class TopAggregationHelper {
* @throws IllegalStateException if no sub-aggregation has been added
* @return the aggregation, that can be added on top level of the elasticsearch request
*/
public FilterAggregationBuilder buildTopAggregation(String topAggregationName, TopAggregationDefinition topAggregation,
public FilterAggregationBuilder buildTopAggregation(String topAggregationName, TopAggregationDefinition<?> topAggregation,
Consumer<BoolQueryBuilder> extraFilters, Consumer<FilterAggregationBuilder> subAggregations) {
BoolQueryBuilder filter = filterComputer.getTopAggregationFilter(topAggregation)
.orElseGet(QueryBuilders::boolQuery);
@@ -79,10 +79,11 @@ public class TopAggregationHelper {

/**
* Same as {@link #buildTopAggregation(String, TopAggregationDefinition, Consumer, Consumer)} with built-in addition of a
* top-term sub aggregation based field defined by {@link TopAggregationDefinition#getFieldName()}.
* top-term sub aggregation based field defined by {@link TopAggregationDefinition.FilterScope#getFieldName()} of
* {@link TopAggregationDefinition#getFilterScope()}.
*/
public FilterAggregationBuilder buildTermTopAggregation(String topAggregationName,
TopAggregationDefinition topAggregation, @Nullable Integer numberOfTerms,
TopAggregationDefinition<?> topAggregation, @Nullable Integer numberOfTerms,
Consumer<BoolQueryBuilder> extraFilters, Consumer<FilterAggregationBuilder> otherSubAggregations) {
Consumer<FilterAggregationBuilder> subAggregations = t -> {
t.subAggregation(subAggregationHelper.buildTermsAggregation(topAggregationName, topAggregation, numberOfTerms));

+ 14
- 24
server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/AllFiltersTest.java View File

@@ -28,11 +28,13 @@ import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.junit.Test;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.FilterScope;

import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.mockito.Mockito.mock;

public class AllFiltersTest {
@Test
@@ -47,15 +49,12 @@ public class AllFiltersTest {

@Test
public void addFilter_fails_if_name_is_null() {
String fieldName = randomAlphabetic(12);
FilterScope filterScope = mock(FilterScope.class);
RequestFiltersComputer.AllFilters allFilters = RequestFiltersComputer.newAllFilters();

Stream.<ThrowingCallable>of(
() -> allFilters.addFilter(null, boolQuery()),
() -> allFilters.addFilter(null, fieldName, boolQuery()))
.forEach(t -> assertThatThrownBy(t)
.isInstanceOf(NullPointerException.class)
.hasMessage("name can't be null"));
assertThatThrownBy(() -> allFilters.addFilter(null, filterScope, boolQuery()))
.isInstanceOf(NullPointerException.class)
.hasMessage("name can't be null");
}

@Test
@@ -65,32 +64,23 @@ public class AllFiltersTest {

assertThatThrownBy(() -> allFilters.addFilter(name, null, boolQuery()))
.isInstanceOf(NullPointerException.class)
.hasMessage("fieldName can't be null");
.hasMessage("filterScope can't be null");
}

@Test
public void addFilter_fails_if_field_with_name_already_exists() {
String name1 = randomAlphabetic(12);
String name2 = randomAlphabetic(15);
String fieldName = randomAlphabetic(16);
String fieldName2 = randomAlphabetic(18);
FilterScope filterScope1 = mock(FilterScope.class);
FilterScope filterScope2 = mock(FilterScope.class);
RequestFiltersComputer.AllFilters allFilters = RequestFiltersComputer.newAllFilters();
allFilters.addFilter(name1, boolQuery());
allFilters.addFilter(name2, fieldName, boolQuery());
allFilters.addFilter(name2, filterScope1, boolQuery());

Stream.<ThrowingCallable>of(
// exact same call
() -> allFilters.addFilter(name1, boolQuery()),
// call with a different fieldName
() -> allFilters.addFilter(name1, fieldName, boolQuery()))
.forEach(t -> assertThatThrownBy(t)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("A filter with name " + name1 + " has already been added"));
Stream.<ThrowingCallable>of(
// exact same call
() -> allFilters.addFilter(name2, fieldName, boolQuery()),
() -> allFilters.addFilter(name2, filterScope1, boolQuery()),
// call with a different fieldName
() -> allFilters.addFilter(name2, fieldName2, boolQuery()))
() -> allFilters.addFilter(name2, filterScope2, boolQuery()))
.forEach(t -> assertThatThrownBy(t)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("A filter with name " + name2 + " has already been added"));
@@ -102,8 +92,8 @@ public class AllFiltersTest {
String name2 = randomAlphabetic(14);
RequestFiltersComputer.AllFilters allFilters = RequestFiltersComputer.newAllFilters();
BoolQueryBuilder query = boolQuery();
allFilters.addFilter(name, query)
.addFilter(name2, null);
allFilters.addFilter(name, mock(FilterScope.class), query)
.addFilter(name2, mock(FilterScope.class), null);

List<QueryBuilder> all = allFilters.stream().collect(Collectors.toList());
assertThat(all).hasSize(1);

+ 150
- 0
server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/NestedFieldFilterScopeTest.java View File

@@ -0,0 +1,150 @@
/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.es.searchrequest;

import org.junit.Test;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.NestedFieldFilterScope;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.SimpleFieldFilterScope;

import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class NestedFieldFilterScopeTest {
@Test
public void constructor_fails_with_NPE_if_fieldName_is_null() {
String nestedFieldName = randomAlphabetic(11);
String value = randomAlphabetic(12);

assertThatThrownBy(() -> new NestedFieldFilterScope<>(null, nestedFieldName, value))
.isInstanceOf(NullPointerException.class)
.hasMessage("fieldName can't be null");
}

@Test
public void constructor_fails_with_NPE_if_nestedFieldName_is_null() {
String fieldName = randomAlphabetic(10);
String value = randomAlphabetic(12);

assertThatThrownBy(() -> new NestedFieldFilterScope<>(fieldName, null, value))
.isInstanceOf(NullPointerException.class)
.hasMessage("nestedFieldName can't be null");
}

@Test
public void constructor_fails_with_NPE_if_value_is_null() {
String fieldName = randomAlphabetic(10);
String nestedFieldName = randomAlphabetic(11);

assertThatThrownBy(() -> new NestedFieldFilterScope<>(fieldName, nestedFieldName, null))
.isInstanceOf(NullPointerException.class)
.hasMessage("value can't be null");
}

@Test
public void verify_getters() {
String fieldName = randomAlphabetic(10);
String nestedFieldName = randomAlphabetic(11);
Object value = new Object();

NestedFieldFilterScope<Object> underTest = new NestedFieldFilterScope<>(fieldName, nestedFieldName, value);

assertThat(underTest.getFieldName()).isEqualTo(fieldName);
assertThat(underTest.getNestedFieldName()).isEqualTo(nestedFieldName);
assertThat(underTest.getNestedFieldValue()).isSameAs(value);
}

@Test
public void verify_equals() {
String fieldName = randomAlphabetic(10);
String nestedFieldName = randomAlphabetic(11);
Object value = new Object();
String fieldName2 = randomAlphabetic(12);
String nestedFieldName2 = randomAlphabetic(13);
Object value2 = new Object();
NestedFieldFilterScope<Object> underTest = new NestedFieldFilterScope<>(fieldName, nestedFieldName, value);

assertThat(underTest)
.isEqualTo(underTest)
.isEqualTo(new NestedFieldFilterScope<>(fieldName, nestedFieldName, value));
assertThat(underTest)
.isNotEqualTo(null)
.isNotEqualTo(new Object())
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName2, nestedFieldName, value))
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName, nestedFieldName2, value))
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName, nestedFieldName, value2))
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName2, nestedFieldName2, value))
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName, nestedFieldName2, value2))
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName2, nestedFieldName, value2))
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName2, nestedFieldName2, value2))
.isNotEqualTo(new SimpleFieldFilterScope(fieldName))
.isNotEqualTo(new SimpleFieldFilterScope(fieldName2));
}

@Test
public void verify_hashcode() {
String fieldName = randomAlphabetic(10);
String nestedFieldName = randomAlphabetic(11);
Object value = new Object();
String fieldName2 = randomAlphabetic(12);
String nestedFieldName2 = randomAlphabetic(13);
Object value2 = new Object();
NestedFieldFilterScope<Object> underTest = new NestedFieldFilterScope<>(fieldName, nestedFieldName, value);

assertThat(underTest.hashCode())
.isEqualTo(underTest.hashCode())
.isEqualTo(new NestedFieldFilterScope<>(fieldName, nestedFieldName, value).hashCode());
assertThat(underTest.hashCode())
.isNotEqualTo(null)
.isNotEqualTo(new Object().hashCode())
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName2, nestedFieldName, value).hashCode())
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName, nestedFieldName2, value).hashCode())
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName, nestedFieldName, value2).hashCode())
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName2, nestedFieldName2, value).hashCode())
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName, nestedFieldName2, value2).hashCode())
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName2, nestedFieldName, value2).hashCode())
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName2, nestedFieldName2, value2).hashCode())
.isNotEqualTo(new SimpleFieldFilterScope(fieldName).hashCode())
.isNotEqualTo(new SimpleFieldFilterScope(fieldName2)).hashCode();
}

@Test
public void verify_intersect() {
String fieldName = randomAlphabetic(10);
String nestedFieldName = randomAlphabetic(11);
Object value = new Object();
String fieldName2 = randomAlphabetic(12);
String nestedFieldName2 = randomAlphabetic(13);
Object value2 = new Object();
NestedFieldFilterScope<Object> underTest = new NestedFieldFilterScope<>(fieldName, nestedFieldName, value);

assertThat(underTest.intersect(underTest)).isTrue();
assertThat(underTest.intersect(new NestedFieldFilterScope<>(fieldName, nestedFieldName, value))).isTrue();
assertThat(underTest.intersect(new NestedFieldFilterScope<>(fieldName2, nestedFieldName, value))).isFalse();
assertThat(underTest.intersect(new NestedFieldFilterScope<>(fieldName, nestedFieldName2, value))).isFalse();
assertThat(underTest.intersect(new NestedFieldFilterScope<>(fieldName, nestedFieldName, value2))).isFalse();
assertThat(underTest.intersect(new NestedFieldFilterScope<>(fieldName2, nestedFieldName2, value))).isFalse();
assertThat(underTest.intersect(new NestedFieldFilterScope<>(fieldName, nestedFieldName2, value2))).isFalse();
assertThat(underTest.intersect(new NestedFieldFilterScope<>(fieldName2, nestedFieldName, value2))).isFalse();
assertThat(underTest.intersect(new NestedFieldFilterScope<>(fieldName2, nestedFieldName2, value2))).isFalse();
assertThat(underTest.intersect(new SimpleFieldFilterScope(fieldName))).isFalse();
assertThat(underTest.intersect(new SimpleFieldFilterScope(fieldName2))).isFalse();
}
}

+ 140
- 0
server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/NestedFieldTopAggregationDefinitionTest.java View File

@@ -0,0 +1,140 @@
/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.es.searchrequest;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.Test;
import org.junit.runner.RunWith;

import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

@RunWith(DataProviderRunner.class)
public class NestedFieldTopAggregationDefinitionTest {

public static final Random RANDOM = new Random();

@Test
@UseDataProvider("notOneLevelDeepPaths")
public void constructor_supports_nestedFieldPath_only_one_level_deep(String unsupportedPath) {
String value = randomAlphabetic(7);
boolean sticky = RANDOM.nextBoolean();

assertThatThrownBy(() -> new NestedFieldTopAggregationDefinition<>(unsupportedPath, value, sticky))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Field path should have only one dot: " + unsupportedPath);
}

@DataProvider
public static Object[][] notOneLevelDeepPaths() {
return new Object[][] {
{""},
{" "},
{".."},
{"a.b."},
{"a.b.c"},
{".b.c"},
{"..."}
};
}

@Test
@UseDataProvider("emptyFieldNames")
public void constructor_fails_with_IAE_if_empty_field_name(String unsupportedPath, List<String> expectedParsedFieldNames) {
String value = randomAlphabetic(7);

assertThatThrownBy(() -> new NestedFieldTopAggregationDefinition<>(unsupportedPath, value, true))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("field path \"" + unsupportedPath + "\" should have exactly 2 non empty field names, got: " + expectedParsedFieldNames);
}

@DataProvider
public static Object[][] emptyFieldNames() {
String str1 = randomAlphabetic(6);
return new Object[][] {
{".", emptyList()},
{" . ", emptyList()},
{str1 + ".", singletonList(str1)},
{str1 + ". ", singletonList(str1)},
{"." + str1, singletonList(str1)},
{" . " + str1, singletonList(str1)}
};
}

@Test
public void constructor_parses_nested_field_path() {
String fieldName = randomAlphabetic(5);
String nestedFieldName = randomAlphabetic(6);
String value = randomAlphabetic(7);
boolean sticky = RANDOM.nextBoolean();
NestedFieldTopAggregationDefinition<String> underTest = new NestedFieldTopAggregationDefinition<>(fieldName + "." + nestedFieldName, value, sticky);

assertThat(underTest.getFilterScope().getFieldName()).isEqualTo(fieldName);
assertThat(underTest.getFilterScope().getNestedFieldName()).isEqualTo(nestedFieldName);
assertThat(underTest.getFilterScope().getNestedFieldValue()).isEqualTo(value);
assertThat(underTest.isSticky()).isEqualTo(sticky);
}

@Test
public void constructor_fails_with_NPE_if_nestedFieldPath_is_null() {
String value = randomAlphabetic(7);
boolean sticky = RANDOM.nextBoolean();

assertThatThrownBy(() -> new NestedFieldTopAggregationDefinition<>(null, value, sticky))
.isInstanceOf(NullPointerException.class)
.hasMessage("nestedFieldPath can't be null");
}

@Test
public void constructor_fails_with_NPE_if_value_is_null() {
String value = randomAlphabetic(7);
boolean sticky = RANDOM.nextBoolean();

assertThatThrownBy(() -> new NestedFieldTopAggregationDefinition<>(value, null, sticky))
.isInstanceOf(NullPointerException.class)
.hasMessage("value can't be null");
}

@Test
public void getFilterScope_always_returns_the_same_instance() {
String fieldName = randomAlphabetic(5);
String nestedFieldName = randomAlphabetic(6);
String value = randomAlphabetic(7);
boolean sticky = RANDOM.nextBoolean();
NestedFieldTopAggregationDefinition<String> underTest = new NestedFieldTopAggregationDefinition<>(fieldName + "." + nestedFieldName, value, sticky);

Set<TopAggregationDefinition.FilterScope> filterScopes = IntStream.range(0, 2 + RANDOM.nextInt(200))
.mapToObj(i -> underTest.getFilterScope())
.collect(Collectors.toSet());

assertThat(filterScopes).hasSize(1);
}

}

+ 289
- 59
server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/RequestFiltersComputerTest.java View File

@@ -31,14 +31,20 @@ import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.junit.Test;
import org.mockito.Mockito;
import org.sonar.server.es.searchrequest.RequestFiltersComputer.AllFilters;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.FilterScope;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.NestedFieldFilterScope;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.SimpleFieldFilterScope;

import static com.google.common.base.Preconditions.checkState;
import static java.util.stream.Collectors.toSet;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.mockito.Mockito.mock;
import static org.sonar.server.es.searchrequest.RequestFiltersComputer.newAllFilters;
import static org.sonar.server.es.searchrequest.TopAggregationDefinition.NON_STICKY;
import static org.sonar.server.es.searchrequest.TopAggregationDefinition.STICKY;

public class RequestFiltersComputerTest {

@@ -48,25 +54,25 @@ public class RequestFiltersComputerTest {
public void getTopAggregationFilters_fails_with_IAE_when_no_TopAggregation_provided_in_constructor() {
RequestFiltersComputer underTest = new RequestFiltersComputer(newAllFilters(), Collections.emptySet());

assertThatThrownBy(() -> underTest.getTopAggregationFilter(Mockito.mock(TopAggregationDefinition.class)))
assertThatThrownBy(() -> underTest.getTopAggregationFilter(mock(TopAggregationDefinition.class)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("topAggregation must have been declared in constructor");
}

@Test
public void getTopAggregationFilters_fails_with_IAE_when_TopAggregation_was_not_provided_in_constructor() {
Set<TopAggregationDefinition> atLeastOneTopAggs = randomNonEmptyTopAggregations(RANDOM::nextBoolean);
Set<TopAggregationDefinition<?>> atLeastOneTopAggs = randomNonEmptyTopAggregations(RANDOM::nextBoolean);
RequestFiltersComputer underTest = new RequestFiltersComputer(newAllFilters(), atLeastOneTopAggs);

atLeastOneTopAggs.forEach(underTest::getTopAggregationFilter);
assertThatThrownBy(() -> underTest.getTopAggregationFilter(Mockito.mock(TopAggregationDefinition.class)))
assertThatThrownBy(() -> underTest.getTopAggregationFilter(mock(TopAggregationDefinition.class)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("topAggregation must have been declared in constructor");
}

@Test
public void getQueryFilters_returns_empty_if_AllFilters_is_empty() {
Set<TopAggregationDefinition> atLeastOneTopAggs = randomNonEmptyTopAggregations(RANDOM::nextBoolean);
Set<TopAggregationDefinition<?>> atLeastOneTopAggs = randomNonEmptyTopAggregations(RANDOM::nextBoolean);
RequestFiltersComputer underTest = new RequestFiltersComputer(newAllFilters(), atLeastOneTopAggs);

assertThat(underTest.getQueryFilters()).isEmpty();
@@ -74,7 +80,7 @@ public class RequestFiltersComputerTest {

@Test
public void getPostFilters_returns_empty_if_AllFilters_is_empty() {
Set<TopAggregationDefinition> atLeastOneTopAggs = randomNonEmptyTopAggregations(RANDOM::nextBoolean);
Set<TopAggregationDefinition<?>> atLeastOneTopAggs = randomNonEmptyTopAggregations(RANDOM::nextBoolean);
RequestFiltersComputer underTest = new RequestFiltersComputer(newAllFilters(), atLeastOneTopAggs);

assertThat(underTest.getPostFilters()).isEmpty();
@@ -82,7 +88,7 @@ public class RequestFiltersComputerTest {

@Test
public void getTopAggregationFilter_returns_empty_if_AllFilters_is_empty() {
Set<TopAggregationDefinition> atLeastOneTopAggs = randomNonEmptyTopAggregations(RANDOM::nextBoolean);
Set<TopAggregationDefinition<?>> atLeastOneTopAggs = randomNonEmptyTopAggregations(RANDOM::nextBoolean);
RequestFiltersComputer underTest = new RequestFiltersComputer(newAllFilters(), atLeastOneTopAggs);

atLeastOneTopAggs.forEach(topAgg -> assertThat(underTest.getTopAggregationFilter(topAgg)).isEmpty());
@@ -107,7 +113,7 @@ public class RequestFiltersComputerTest {
@Test
public void getQueryFilters_contains_all_filters_when_no_declared_sticky_topAggregation() {
AllFilters allFilters = randomNonEmptyAllFilters();
Set<TopAggregationDefinition> atLeastOneNonStickyTopAggs = randomNonEmptyTopAggregations(() -> false);
Set<TopAggregationDefinition<?>> atLeastOneNonStickyTopAggs = randomNonEmptyTopAggregations(() -> false);
RequestFiltersComputer underTest = new RequestFiltersComputer(allFilters, atLeastOneNonStickyTopAggs);

assertThat(underTest.getQueryFilters().get()).isEqualTo(toBoolQuery(allFilters.stream()));
@@ -116,7 +122,7 @@ public class RequestFiltersComputerTest {
@Test
public void getPostFilters_returns_empty_when_no_declared_sticky_topAggregation() {
AllFilters allFilters = randomNonEmptyAllFilters();
Set<TopAggregationDefinition> atLeastOneNonStickyTopAggs = randomNonEmptyTopAggregations(() -> false);
Set<TopAggregationDefinition<?>> atLeastOneNonStickyTopAggs = randomNonEmptyTopAggregations(() -> false);
RequestFiltersComputer underTest = new RequestFiltersComputer(allFilters, atLeastOneNonStickyTopAggs);

assertThat(underTest.getPostFilters()).isEmpty();
@@ -125,7 +131,7 @@ public class RequestFiltersComputerTest {
@Test
public void getTopAggregationFilters_return_empty_when_no_declared_sticky_topAggregation() {
AllFilters allFilters = randomNonEmptyAllFilters();
Set<TopAggregationDefinition> atLeastOneNonStickyTopAggs = randomNonEmptyTopAggregations(() -> false);
Set<TopAggregationDefinition<?>> atLeastOneNonStickyTopAggs = randomNonEmptyTopAggregations(() -> false);
RequestFiltersComputer underTest = new RequestFiltersComputer(allFilters, atLeastOneNonStickyTopAggs);

atLeastOneNonStickyTopAggs.forEach(topAgg -> assertThat(underTest.getTopAggregationFilter(topAgg)).isEmpty());
@@ -136,32 +142,36 @@ public class RequestFiltersComputerTest {
AllFilters allFilters = newAllFilters();
// has topAggs and two filters
String field1 = "field1";
TopAggregationDefinition stickyTopAggField1 = new TopAggregationDef(field1, true);
TopAggregationDefinition nonStickyTopAggField1 = new TopAggregationDef(field1, false);
SimpleFieldFilterScope filterScopeField1 = new SimpleFieldFilterScope(field1);
SimpleFieldTopAggregationDefinition stickyTopAggField1 = new SimpleFieldTopAggregationDefinition(field1, STICKY);
SimpleFieldTopAggregationDefinition nonStickyTopAggField1 = new SimpleFieldTopAggregationDefinition(field1, NON_STICKY);
QueryBuilder filterField1_1 = newQuery();
QueryBuilder filterField1_2 = newQuery();
allFilters.addFilter(field1, filterField1_1);
allFilters.addFilter(field1 + "_2", field1, filterField1_2);
allFilters.addFilter("filter_field1_1", filterScopeField1, filterField1_1);
allFilters.addFilter("filter_field1_2", filterScopeField1, filterField1_2);
// has topAggs and one filter
String field2 = "field2";
TopAggregationDefinition stickyTopAggField2 = new TopAggregationDef(field2, true);
TopAggregationDefinition nonStickyTopAggField2 = new TopAggregationDef(field2, false);
SimpleFieldFilterScope filterScopeField2 = new SimpleFieldFilterScope(field2);
SimpleFieldTopAggregationDefinition stickyTopAggField2 = new SimpleFieldTopAggregationDefinition(field2, STICKY);
SimpleFieldTopAggregationDefinition nonStickyTopAggField2 = new SimpleFieldTopAggregationDefinition(field2, NON_STICKY);
QueryBuilder filterField2 = newQuery();
allFilters.addFilter(field2, filterField2);
allFilters.addFilter("filter_field2", filterScopeField2, filterField2);
// has only non-sticky top-agg and one filter
String field3 = "field3";
TopAggregationDefinition nonStickyTopAggField3 = new TopAggregationDef(field3, false);
SimpleFieldFilterScope filterScopeField3 = new SimpleFieldFilterScope(field3);
SimpleFieldTopAggregationDefinition nonStickyTopAggField3 = new SimpleFieldTopAggregationDefinition(field3, NON_STICKY);
QueryBuilder filterField3 = newQuery();
allFilters.addFilter(field3, filterField3);
allFilters.addFilter("filter_field3", filterScopeField3, filterField3);
// has one filter but no top agg
String field4 = "field4";
SimpleFieldFilterScope filterScopeField4 = new SimpleFieldFilterScope(field4);
QueryBuilder filterField4 = newQuery();
allFilters.addFilter(field4, filterField4);
allFilters.addFilter("filter_field4", filterScopeField4, filterField4);
// has top-aggs by no filter
String field5 = "field5";
TopAggregationDefinition stickyTopAggField5 = new TopAggregationDef(field5, true);
TopAggregationDefinition nonStickyTopAggField5 = new TopAggregationDef(field5, false);
Set<TopAggregationDefinition> declaredTopAggregations = ImmutableSet.of(
SimpleFieldTopAggregationDefinition stickyTopAggField5 = new SimpleFieldTopAggregationDefinition(field5, STICKY);
SimpleFieldTopAggregationDefinition nonStickyTopAggField5 = new SimpleFieldTopAggregationDefinition(field5, NON_STICKY);
Set<TopAggregationDefinition<?>> declaredTopAggregations = ImmutableSet.of(
stickyTopAggField1, nonStickyTopAggField1,
stickyTopAggField2, nonStickyTopAggField2,
nonStickyTopAggField3,
@@ -170,45 +180,240 @@ public class RequestFiltersComputerTest {
RequestFiltersComputer underTest = new RequestFiltersComputer(allFilters, declaredTopAggregations);

assertThat(underTest.getQueryFilters().get()).isEqualTo(toBoolQuery(filterField3, filterField4));
BoolQueryBuilder postFilterQuery = toBoolQuery(filterField1_1, filterField1_2, filterField2);
assertThat(underTest.getPostFilters().get()).isEqualTo(postFilterQuery);
assertThat(underTest.getTopAggregationFilter(stickyTopAggField1).get()).isEqualTo(toBoolQuery(filterField2));
assertThat(underTest.getTopAggregationFilter(nonStickyTopAggField1).get()).isEqualTo(postFilterQuery);
assertThat(underTest.getTopAggregationFilter(stickyTopAggField2).get()).isEqualTo(toBoolQuery(filterField1_1, filterField1_2));
assertThat(underTest.getTopAggregationFilter(nonStickyTopAggField2).get()).isEqualTo(postFilterQuery);
assertThat(underTest.getTopAggregationFilter(nonStickyTopAggField3).get()).isEqualTo(postFilterQuery);
assertThat(underTest.getTopAggregationFilter(stickyTopAggField5).get()).isEqualTo(postFilterQuery);
assertThat(underTest.getTopAggregationFilter(nonStickyTopAggField5).get()).isEqualTo(postFilterQuery);
QueryBuilder[] postFilters = {filterField1_1, filterField1_2, filterField2};
assertThat(underTest.getPostFilters().get()).isEqualTo(toBoolQuery(postFilters));
assertTopAggregationFilter(underTest, stickyTopAggField1, filterField2);
assertTopAggregationFilter(underTest, nonStickyTopAggField1, postFilters);
assertTopAggregationFilter(underTest, stickyTopAggField2, filterField1_1, filterField1_2);
assertTopAggregationFilter(underTest, nonStickyTopAggField2, postFilters);
assertTopAggregationFilter(underTest, nonStickyTopAggField3, postFilters);
assertTopAggregationFilter(underTest, stickyTopAggField5, postFilters);
assertTopAggregationFilter(underTest, nonStickyTopAggField5, postFilters);
}

@Test
public void getTopAggregationFilters_returns_empty_on_sticky_TopAgg_when_no_other_sticky_TopAgg() {
public void getTopAggregationFilters_returns_empty_on_sticky_field_TopAgg_when_no_other_sticky_TopAgg() {
AllFilters allFilters = newAllFilters();
// has topAggs and two filters
String field1 = "field1";
TopAggregationDefinition stickyTopAggField1 = new TopAggregationDef(field1, true);
TopAggregationDefinition nonStickyTopAggField1 = new TopAggregationDef(field1, false);
SimpleFieldFilterScope filterScopeField1 = new SimpleFieldFilterScope(field1);
SimpleFieldTopAggregationDefinition stickyTopAggField1 = new SimpleFieldTopAggregationDefinition(field1, STICKY);
SimpleFieldTopAggregationDefinition nonStickyTopAggField1 = new SimpleFieldTopAggregationDefinition(field1, NON_STICKY);
QueryBuilder filterField1_1 = newQuery();
QueryBuilder filterField1_2 = newQuery();
allFilters.addFilter(field1, filterField1_1);
allFilters.addFilter(field1 + "_2", field1, filterField1_2);
allFilters.addFilter("filter_field1_1", filterScopeField1, filterField1_1);
allFilters.addFilter("filter_field1_2", filterScopeField1, filterField1_2);
// has only non-sticky top-agg and one filter
String field2 = "field2";
TopAggregationDefinition nonStickyTopAggField2 = new TopAggregationDef(field2, false);
SimpleFieldFilterScope filterScopeField2 = new SimpleFieldFilterScope(field2);
SimpleFieldTopAggregationDefinition nonStickyTopAggField2 = new SimpleFieldTopAggregationDefinition(field2, NON_STICKY);
QueryBuilder filterField2 = newQuery();
allFilters.addFilter(field2, filterField2);
Set<TopAggregationDefinition> declaredTopAggregations = ImmutableSet.of(
allFilters.addFilter("filter_field2", filterScopeField2, filterField2);
Set<TopAggregationDefinition<?>> declaredTopAggregations = ImmutableSet.of(
stickyTopAggField1, nonStickyTopAggField1,
nonStickyTopAggField2);

RequestFiltersComputer underTest = new RequestFiltersComputer(allFilters, declaredTopAggregations);

assertThat(underTest.getQueryFilters().get()).isEqualTo(toBoolQuery(filterField2));
BoolQueryBuilder postFilterQuery = toBoolQuery(filterField1_1, filterField1_2);
assertThat(underTest.getPostFilters().get()).isEqualTo(postFilterQuery);
QueryBuilder[] postFilters = {filterField1_1, filterField1_2};
assertThat(underTest.getPostFilters().get()).isEqualTo(toBoolQuery(postFilters));
assertThat(underTest.getTopAggregationFilter(stickyTopAggField1)).isEmpty();
assertThat(underTest.getTopAggregationFilter(nonStickyTopAggField1).get()).isEqualTo(postFilterQuery);
assertThat(underTest.getTopAggregationFilter(nonStickyTopAggField2).get()).isEqualTo(postFilterQuery);
assertTopAggregationFilter(underTest, nonStickyTopAggField1, postFilters);
assertTopAggregationFilter(underTest, nonStickyTopAggField2, postFilters);
}

@Test
public void filters_on_nestedField_of_sticky_TopAggregation_go_to_PostFilters_and_TopAgg_Filters_on_other_values_of_same_nestField() {
String field1 = "field";
String nestField = "nestedField";
String nestField_value1 = "nestedField_value1";
String nestField_value2 = "nestedField_value2";
String nestField_value3 = "nestedField_value3";
String nestField_value4 = "nestedField_value4";
String nestField_value5 = "nestedField_value5";

AllFilters allFilters = newAllFilters();
// has topAggs and two filters
NestedFieldFilterScope<String> filterScopeNestField_value1 = new NestedFieldFilterScope<>(field1, nestField, nestField_value1);
NestedFieldTopAggregationDefinition<String> stickyTopAggField1 = newNestedFieldTopAggDef(field1, nestField, nestField_value1, STICKY);
NestedFieldTopAggregationDefinition<String> nonStickyTopAggField1 = newNestedFieldTopAggDef(field1, nestField, nestField_value1, NON_STICKY);
QueryBuilder filterField1_1 = newQuery();
QueryBuilder filterField1_2 = newQuery();
allFilters.addFilter("filter_field1_1", filterScopeNestField_value1, filterField1_1);
allFilters.addFilter("filter_field1_2", filterScopeNestField_value1, filterField1_2);
// has topAggs and one filter
NestedFieldFilterScope<String> filterScopeNestField_value2 = new NestedFieldFilterScope<>(field1, nestField, nestField_value2);
NestedFieldTopAggregationDefinition<String> stickyTopAggField2 = newNestedFieldTopAggDef(field1, nestField, nestField_value2, STICKY);
NestedFieldTopAggregationDefinition<String> nonStickyTopAggField2 = newNestedFieldTopAggDef(field1, nestField, nestField_value2, NON_STICKY);
QueryBuilder filterField2 = newQuery();
allFilters.addFilter("filter_field2", filterScopeNestField_value2, filterField2);
// has only non-sticky top-agg and one filter
NestedFieldFilterScope<String> filterScopeField3 = new NestedFieldFilterScope<>(field1, nestField, nestField_value3);
NestedFieldTopAggregationDefinition<String> nonStickyTopAggField3 = newNestedFieldTopAggDef(field1, nestField, nestField_value3, NON_STICKY);
QueryBuilder filterField3 = newQuery();
allFilters.addFilter("filter_field3", filterScopeField3, filterField3);
// has one filter but no top agg
NestedFieldFilterScope<String> filterScopeField4 = new NestedFieldFilterScope<>(field1, nestField, nestField_value4);
QueryBuilder filterField4 = newQuery();
allFilters.addFilter("filter_field4", filterScopeField4, filterField4);
// has top-aggs by no filter
String field5 = "field5";
NestedFieldTopAggregationDefinition<String> stickyTopAggField5 = newNestedFieldTopAggDef(field1, nestField, nestField_value5, STICKY);
NestedFieldTopAggregationDefinition<String> nonStickyTopAggField5 = newNestedFieldTopAggDef(field1, nestField, nestField_value5, NON_STICKY);
Set<TopAggregationDefinition<?>> declaredTopAggregations = ImmutableSet.of(
stickyTopAggField1, nonStickyTopAggField1,
stickyTopAggField2, nonStickyTopAggField2,
nonStickyTopAggField3,
stickyTopAggField5, nonStickyTopAggField5);

RequestFiltersComputer underTest = new RequestFiltersComputer(allFilters, declaredTopAggregations);

assertThat(underTest.getQueryFilters().get()).isEqualTo(toBoolQuery(filterField3, filterField4));
QueryBuilder[] postFilters = {filterField1_1, filterField1_2, filterField2};
assertThat(underTest.getPostFilters().get()).isEqualTo(toBoolQuery(postFilters));
assertTopAggregationFilter(underTest, stickyTopAggField1, filterField2);
assertTopAggregationFilter(underTest, nonStickyTopAggField1, postFilters);
assertTopAggregationFilter(underTest, stickyTopAggField2, filterField1_1, filterField1_2);
assertTopAggregationFilter(underTest, nonStickyTopAggField2, postFilters);
assertTopAggregationFilter(underTest, nonStickyTopAggField3, postFilters);
assertTopAggregationFilter(underTest, stickyTopAggField5, postFilters);
assertTopAggregationFilter(underTest, nonStickyTopAggField5, postFilters);
}

@Test
public void filters_on_nestedField_of_sticky_TopAggregation_go_to_PostFilters_and_TopAgg_Filters_on_other_fields() {
String field1 = "field1";
String field2 = "field2";
String field3 = "field3";
String nestField = "nestedField";
String nestField_value1 = "nestedField_value1";
String nestField_value2 = "nestedField_value2";

AllFilters allFilters = newAllFilters();
// filter without top aggregation
QueryBuilder queryFilter = newQuery("query_filter");
allFilters.addFilter("query_filter", new SimpleFieldFilterScope("text"), queryFilter);
// nestedField of field1 with value1: has topAggs and two filters
NestedFieldFilterScope<String> filterScopeNestField1_value1 = new NestedFieldFilterScope<>(field1, nestField, nestField_value1);
NestedFieldTopAggregationDefinition<String> stickyTopAggNestedField1_value1 = newNestedFieldTopAggDef(field1, nestField, nestField_value1, STICKY);
NestedFieldTopAggregationDefinition<String> nonStickyTopAggNestedField1_value1 = newNestedFieldTopAggDef(field1, nestField, nestField_value1, NON_STICKY);
QueryBuilder filterNestedField1_value1_1 = newQuery("filterNestedField1_value1_1");
QueryBuilder filterNestedField1_value1_2 = newQuery("filterNestedField1_value1_2");
allFilters.addFilter("filter_nested_field1_value1_1", filterScopeNestField1_value1, filterNestedField1_value1_1);
allFilters.addFilter("filter_nested_field1_value1_2", filterScopeNestField1_value1, filterNestedField1_value1_2);
// nestedField of field1 with value2: has topAggs and two filters
NestedFieldFilterScope<String> filterScopeNestField1_value2 = new NestedFieldFilterScope<>(field1, nestField, nestField_value2);
NestedFieldTopAggregationDefinition<String> stickyTopAggNestedField1_value2 = newNestedFieldTopAggDef(field1, nestField, nestField_value2, STICKY);
NestedFieldTopAggregationDefinition<String> nonStickyTopAggNestedField1_value2 = newNestedFieldTopAggDef(field1, nestField, nestField_value2, NON_STICKY);
QueryBuilder filterNestedField1_value2_1 = newQuery("filterNestedField1_value2_1");
QueryBuilder filterNestedField1_value2_2 = newQuery("filterNestedField1_value2_2");
allFilters.addFilter("filter_nested_field1_value2_1", filterScopeNestField1_value2, filterNestedField1_value2_1);
allFilters.addFilter("filter_nested_field1_value2_2", filterScopeNestField1_value2, filterNestedField1_value2_2);
// [EDGE CASE] topAgg directly on field1: has topAggs and two filters
SimpleFieldFilterScope filterScopeField1 = new SimpleFieldFilterScope(field1);
SimpleFieldTopAggregationDefinition stickyTopAggField1 = new SimpleFieldTopAggregationDefinition(field1, STICKY);
SimpleFieldTopAggregationDefinition nonStickyTopAggField1 = new SimpleFieldTopAggregationDefinition(field1, NON_STICKY);
QueryBuilder filterField1_1 = newQuery("filterField1_1");
QueryBuilder filterField1_2 = newQuery("filterField1_2");
allFilters.addFilter("filter_field1_1", filterScopeField1, filterField1_1);
allFilters.addFilter("filter_field1_2", filterScopeField1, filterField1_2);
// other field: has topAggs and two filters too
SimpleFieldFilterScope filterScopeField2 = new SimpleFieldFilterScope(field2);
SimpleFieldTopAggregationDefinition stickyTopAggField2 = new SimpleFieldTopAggregationDefinition(field2, STICKY);
SimpleFieldTopAggregationDefinition nonStickyTopAggField2 = new SimpleFieldTopAggregationDefinition(field2, NON_STICKY);
QueryBuilder filterField2_1 = newQuery("filterField2_1");
QueryBuilder filterField2_2 = newQuery("filterField2_2");
allFilters.addFilter("filter_field2_1", filterScopeField2, filterField2_1);
allFilters.addFilter("filter_field2_2", filterScopeField2, filterField2_2);
// nestedField of another field (even though nestedField name and values are the same): has topAggs and two filters
NestedFieldFilterScope<String> filterScopeNestField3_value = new NestedFieldFilterScope<>(field3, nestField, nestField_value1);
NestedFieldTopAggregationDefinition<String> stickyTopAggNestedField3 = newNestedFieldTopAggDef(field3, nestField, nestField_value1, STICKY);
NestedFieldTopAggregationDefinition<String> nonStickyTopAggNestedField3 = newNestedFieldTopAggDef(field3, nestField, nestField_value1, NON_STICKY);
QueryBuilder filterNestedField3_1 = newQuery("filterNestedField3_1");
QueryBuilder filterNestedField3_2 = newQuery("filterNestedField3_2");
allFilters.addFilter("filter_nested_field3_1", filterScopeNestField3_value, filterNestedField3_1);
allFilters.addFilter("filter_nested_field3_2", filterScopeNestField3_value, filterNestedField3_2);
Set<TopAggregationDefinition<?>> declaredTopAggregations = ImmutableSet.of(
stickyTopAggNestedField1_value1, nonStickyTopAggNestedField1_value1,
stickyTopAggNestedField1_value2, nonStickyTopAggNestedField1_value2,
stickyTopAggField1, nonStickyTopAggField1,
stickyTopAggField2, nonStickyTopAggField2,
stickyTopAggNestedField3, nonStickyTopAggNestedField3);

RequestFiltersComputer underTest = new RequestFiltersComputer(allFilters, declaredTopAggregations);

assertThat(underTest.getQueryFilters().get()).isEqualTo(toBoolQuery(queryFilter));
QueryBuilder[] postFilters = {
filterNestedField1_value1_1, filterNestedField1_value1_2,
filterNestedField1_value2_1, filterNestedField1_value2_2,
filterField1_1, filterField1_2,
filterField2_1, filterField2_2,
filterNestedField3_1, filterNestedField3_2};
assertThat(underTest.getPostFilters().get()).isEqualTo(toBoolQuery(postFilters));
assertTopAggregationFilter(underTest, stickyTopAggNestedField1_value1,
filterNestedField1_value2_1, filterNestedField1_value2_2,
filterField1_1, filterField1_2,
filterField2_1, filterField2_2,
filterNestedField3_1, filterNestedField3_2);
assertTopAggregationFilter(underTest, nonStickyTopAggNestedField1_value1, postFilters);
assertTopAggregationFilter(underTest, stickyTopAggNestedField1_value2,
filterNestedField1_value1_1, filterNestedField1_value1_2,
filterField1_1, filterField1_2,
filterField2_1, filterField2_2,
filterNestedField3_1, filterNestedField3_2);
assertTopAggregationFilter(underTest, nonStickyTopAggNestedField1_value2, postFilters);
assertTopAggregationFilter(underTest, stickyTopAggField1,
filterField2_1, filterField2_2,
filterNestedField3_1, filterNestedField3_2);
assertTopAggregationFilter(underTest, nonStickyTopAggField1, postFilters);
assertTopAggregationFilter(underTest, stickyTopAggField2,
filterNestedField1_value1_1, filterNestedField1_value1_2,
filterNestedField1_value2_1, filterNestedField1_value2_2,
filterField1_1, filterField1_2,
filterNestedField3_1, filterNestedField3_2);
assertTopAggregationFilter(underTest, nonStickyTopAggField2, postFilters);
assertTopAggregationFilter(underTest, stickyTopAggNestedField3,
filterNestedField1_value1_1, filterNestedField1_value1_2,
filterNestedField1_value2_1, filterNestedField1_value2_2,
filterField1_1, filterField1_2,
filterField2_1, filterField2_2);
assertTopAggregationFilter(underTest, nonStickyTopAggNestedField3, postFilters);
}

@Test
public void getTopAggregationFilters_returns_empty_on_sticky_nestedField_TopAgg_when_no_other_sticky_TopAgg() {
String field1 = "field";
String nestField = "nestedField";
String nestField_value1 = "nestedField_value1";
String nestField_value2 = "nestedField_value2";

AllFilters allFilters = newAllFilters();
// has topAggs and two filters
NestedFieldFilterScope<String> filterScopeField1 = new NestedFieldFilterScope<>(field1, nestField, nestField_value1);
NestedFieldTopAggregationDefinition<String> stickyTopAggField1 = newNestedFieldTopAggDef(field1, nestField, nestField_value1, STICKY);
NestedFieldTopAggregationDefinition<String> nonStickyTopAggField1 = newNestedFieldTopAggDef(field1, nestField, nestField_value1, NON_STICKY);
QueryBuilder filterField1_1 = newQuery();
QueryBuilder filterField1_2 = newQuery();
allFilters.addFilter("filter_field1_1", filterScopeField1, filterField1_1);
allFilters.addFilter("filter_field1_2", filterScopeField1, filterField1_2);
// has only non-sticky top-agg and one filter
NestedFieldFilterScope<String> filterScopeField2 = new NestedFieldFilterScope<>(field1, nestField, nestField_value2);
NestedFieldTopAggregationDefinition<String> nonStickyTopAggField2 = newNestedFieldTopAggDef(field1, nestField, nestField_value2, NON_STICKY);
QueryBuilder filterField2 = newQuery();
allFilters.addFilter("filter_field2", filterScopeField2, filterField2);
Set<TopAggregationDefinition<?>> declaredTopAggregations = ImmutableSet.of(
stickyTopAggField1, nonStickyTopAggField1,
nonStickyTopAggField2);

RequestFiltersComputer underTest = new RequestFiltersComputer(allFilters, declaredTopAggregations);

assertThat(underTest.getQueryFilters().get()).isEqualTo(toBoolQuery(filterField2));
QueryBuilder[] postFilters = {filterField1_1, filterField1_2};
assertThat(underTest.getPostFilters().get()).isEqualTo(toBoolQuery(postFilters));
assertThat(underTest.getTopAggregationFilter(stickyTopAggField1)).isEmpty();
assertTopAggregationFilter(underTest, nonStickyTopAggField1, postFilters);
assertTopAggregationFilter(underTest, nonStickyTopAggField2, postFilters);
}

@Test
@@ -216,34 +421,36 @@ public class RequestFiltersComputerTest {
AllFilters allFilters = newAllFilters();
// has topAggs and two filters
String field1 = "field1";
TopAggregationDefinition stickyTopAggField1 = new TopAggregationDef(field1, true);
TopAggregationDefinition nonStickyTopAggField1 = new TopAggregationDef(field1, false);
SimpleFieldFilterScope filterScopeField1 = new SimpleFieldFilterScope(field1);
SimpleFieldTopAggregationDefinition stickyTopAggField1 = new SimpleFieldTopAggregationDefinition(field1, STICKY);
SimpleFieldTopAggregationDefinition nonStickyTopAggField1 = new SimpleFieldTopAggregationDefinition(field1, NON_STICKY);
QueryBuilder filterField1_1 = newQuery();
QueryBuilder filterField1_2 = newQuery();
allFilters.addFilter(field1, filterField1_1);
allFilters.addFilter(field1 + "_2", field1, filterField1_2);
allFilters.addFilter("filter_field1_1", filterScopeField1, filterField1_1);
allFilters.addFilter("filter_field1_2", filterScopeField1, filterField1_2);
// has only sticky top-agg and one filter
String field2 = "field2";
TopAggregationDefinition stickyTopAggField2 = new TopAggregationDef(field2, true);
SimpleFieldFilterScope filterScopeField2 = new SimpleFieldFilterScope(field2);
SimpleFieldTopAggregationDefinition stickyTopAggField2 = new SimpleFieldTopAggregationDefinition(field2, STICKY);
QueryBuilder filterField2 = newQuery();
allFilters.addFilter(field2, filterField2);
Set<TopAggregationDefinition> declaredTopAggregations = ImmutableSet.of(
allFilters.addFilter("filter_field2", filterScopeField2, filterField2);
Set<TopAggregationDefinition<?>> declaredTopAggregations = ImmutableSet.of(
stickyTopAggField1, nonStickyTopAggField1,
stickyTopAggField2);

RequestFiltersComputer underTest = new RequestFiltersComputer(allFilters, declaredTopAggregations);

assertThat(underTest.getQueryFilters()).isEmpty();
BoolQueryBuilder postFilterQuery = toBoolQuery(filterField1_1, filterField1_2, filterField2);
assertThat(underTest.getPostFilters().get()).isEqualTo(postFilterQuery);
assertThat(underTest.getTopAggregationFilter(stickyTopAggField1).get()).isEqualTo(toBoolQuery(filterField2));
assertThat(underTest.getTopAggregationFilter(nonStickyTopAggField1).get()).isEqualTo(postFilterQuery);
assertThat(underTest.getTopAggregationFilter(stickyTopAggField2).get()).isEqualTo(toBoolQuery(filterField1_1, filterField1_2));
QueryBuilder[] postFilters = {filterField1_1, filterField1_2, filterField2};
assertThat(underTest.getPostFilters().get()).isEqualTo(toBoolQuery(postFilters));
assertTopAggregationFilter(underTest, stickyTopAggField1, filterField2);
assertTopAggregationFilter(underTest, nonStickyTopAggField1, postFilters);
assertTopAggregationFilter(underTest, stickyTopAggField2, filterField1_1, filterField1_2);
}

private static Set<TopAggregationDefinition> randomNonEmptyTopAggregations(Supplier<Boolean> isSticky) {
private static Set<TopAggregationDefinition<?>> randomNonEmptyTopAggregations(Supplier<Boolean> isSticky) {
return IntStream.range(0, 1 + RANDOM.nextInt(20))
.mapToObj(i -> new TopAggregationDef("field_" + i, isSticky.get()))
.mapToObj(i -> new SimpleFieldTopAggregationDefinition("field_" + i, isSticky.get()))
.collect(toSet());
}

@@ -252,19 +459,28 @@ public class RequestFiltersComputerTest {
Stream.of(first), Arrays.stream(others)));
}

private static BoolQueryBuilder toBoolQuery(QueryBuilder[] subQueries) {
return toBoolQuery(Arrays.stream(subQueries));
}

private static BoolQueryBuilder toBoolQuery(Stream<QueryBuilder> stream) {
BoolQueryBuilder res = boolQuery();
stream.forEach(res::must);
checkState(res.hasClauses(), "empty stream is not supported");
return res;
}

private static AllFilters randomNonEmptyAllFilters() {
AllFilters res = newAllFilters();
IntStream.range(0, 1 + RANDOM.nextInt(22))
.forEach(i -> res.addFilter("field_" + i, newQuery()));
.forEach(i -> res.addFilter("filter_" + i, mock(FilterScope.class), newQuery()));
return res;
}

private static NestedFieldTopAggregationDefinition<String> newNestedFieldTopAggDef(String field1, String nestField, String nestField_value1, boolean sticky) {
return new NestedFieldTopAggregationDefinition<>(field1 + "." + nestField, nestField_value1, sticky);
}

private static int queryCounter = 0;

/**
@@ -273,4 +489,18 @@ public class RequestFiltersComputerTest {
private static QueryBuilder newQuery() {
return QueryBuilders.termQuery("query_" + (queryCounter++), "foo");
}

private static QueryBuilder newQuery(String label) {
return QueryBuilders.termQuery("query_" + label, "foo");
}

private static void assertTopAggregationFilter(RequestFiltersComputer underTest,
TopAggregationDefinition<?> topAggregation, QueryBuilder firstFilter, QueryBuilder... otherFilters) {
assertThat(underTest.getTopAggregationFilter(topAggregation).get()).isEqualTo(toBoolQuery(firstFilter, otherFilters));
}

private static void assertTopAggregationFilter(RequestFiltersComputer underTest,
TopAggregationDefinition<?> topAggregation, QueryBuilder[] filters) {
assertThat(underTest.getTopAggregationFilter(topAggregation).get()).isEqualTo(toBoolQuery(filters));
}
}

+ 92
- 0
server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/SimpleFieldFilterScopeTest.java View File

@@ -0,0 +1,92 @@
/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.es.searchrequest;

import org.junit.Test;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.NestedFieldFilterScope;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.SimpleFieldFilterScope;

import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class SimpleFieldFilterScopeTest {
@Test
public void constructor_fails_with_NPE_if_fieldName_is_null() {
assertThatThrownBy(() -> new SimpleFieldFilterScope(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("fieldName can't be null");
}

@Test
public void getFieldName() {
String fieldName = randomAlphabetic(12);
SimpleFieldFilterScope underTest = new SimpleFieldFilterScope(fieldName);

assertThat(underTest.getFieldName()).isEqualTo(fieldName);
}

@Test
public void verify_equals() {
String fieldName1 = randomAlphabetic(11);
String fieldName2 = randomAlphabetic(12);
SimpleFieldFilterScope underTest = new SimpleFieldFilterScope(fieldName1);

assertThat(underTest)
.isEqualTo(underTest)
.isEqualTo(new SimpleFieldFilterScope(fieldName1));
assertThat(underTest)
.isNotEqualTo(null)
.isNotEqualTo(new Object())
.isNotEqualTo(new SimpleFieldFilterScope(fieldName2))
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName1, "foo", "bar"))
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName2, "foo", "bar"));
}

@Test
public void verify_hashcode() {
String fieldName1 = randomAlphabetic(11);
String fieldName2 = randomAlphabetic(12);
SimpleFieldFilterScope underTest = new SimpleFieldFilterScope(fieldName1);

assertThat(underTest.hashCode())
.isEqualTo(underTest.hashCode())
.isEqualTo(new SimpleFieldFilterScope(fieldName1).hashCode());
assertThat(underTest.hashCode())
.isNotEqualTo(null)
.isNotEqualTo(new Object().hashCode())
.isNotEqualTo(new SimpleFieldFilterScope(fieldName2).hashCode())
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName1, "foo", "bar").hashCode())
.isNotEqualTo(new NestedFieldFilterScope<>(fieldName1, "foo", "bar").hashCode());
}

@Test
public void verify_intersect() {
String fieldName1 = randomAlphabetic(11);
String fieldName2 = randomAlphabetic(12);
SimpleFieldFilterScope underTest = new SimpleFieldFilterScope(fieldName1);

assertThat(underTest.intersect(underTest)).isTrue();
assertThat(underTest.intersect(new SimpleFieldFilterScope(fieldName1))).isTrue();
assertThat(underTest.intersect(new SimpleFieldFilterScope(fieldName2))).isFalse();
assertThat(underTest.intersect(new NestedFieldFilterScope<>(fieldName1, "foo", "bar"))).isTrue();
assertThat(underTest.intersect(new NestedFieldFilterScope<>(fieldName2, "foo", "bar"))).isFalse();
}
}

server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/TopAggregationDefTest.java → server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/SimpleFieldTopAggregationDefinitionTest.java View File

@@ -20,33 +20,52 @@
package org.sonar.server.es.searchrequest;

import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;

public class TopAggregationDefTest {
public class SimpleFieldTopAggregationDefinitionTest {
private static final Random RANDOM = new Random();

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void fieldName_cannot_be_null() {
boolean sticky = new Random().nextBoolean();
boolean sticky = RANDOM.nextBoolean();
expectedException.expect(NullPointerException.class);
expectedException.expectMessage("fieldName can't be null");

new TopAggregationDef(null, sticky);
new SimpleFieldTopAggregationDefinition(null, sticky);
}

@Test
public void getters() {
String fieldName = RandomStringUtils.randomAlphabetic(12);
boolean sticky = new Random().nextBoolean();
TopAggregationDef underTest = new TopAggregationDef(fieldName, sticky);
SimpleFieldTopAggregationDefinition underTest = new SimpleFieldTopAggregationDefinition(fieldName, sticky);

assertThat(underTest.getFieldName()).isEqualTo(fieldName);
assertThat(underTest.getFilterScope().getFieldName()).isEqualTo(fieldName);
assertThat(underTest.isSticky()).isEqualTo(sticky);
}

@Test
public void getFilterScope_always_returns_the_same_instance() {
String fieldName = randomAlphabetic(12);
boolean sticky = RANDOM.nextBoolean();
SimpleFieldTopAggregationDefinition underTest = new SimpleFieldTopAggregationDefinition(fieldName, sticky);

Set<TopAggregationDefinition.FilterScope> filterScopes = IntStream.range(0, 2 + RANDOM.nextInt(200))
.mapToObj(i -> underTest.getFilterScope())
.collect(Collectors.toSet());

assertThat(filterScopes).hasSize(1);
}
}

+ 13
- 13
server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/SubAggregationHelperTest.java View File

@@ -45,7 +45,7 @@ public class SubAggregationHelperTest {
@Test
public void buildTermsAggregation_adds_term_subaggregation_with_minDoc_1_and_default_sort() {
String aggName = randomAlphabetic(10);
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);

Stream.of(
underTest,
@@ -54,7 +54,7 @@ public class SubAggregationHelperTest {
TermsAggregationBuilder agg = t.buildTermsAggregation(aggName, topAggregation, null);

assertThat(agg.getName()).isEqualTo(aggName);
assertThat(agg.field()).isEqualTo(topAggregation.getFieldName());
assertThat(agg.field()).isEqualTo(topAggregation.getFilterScope().getFieldName());
assertThat(agg.size()).isEqualTo(DEFAULT_BUCKET_SIZE);
assertThat(agg.minDocCount()).isEqualTo(1);
assertThat(agg.order()).isEqualTo(BucketOrder.compound(SQ_DEFAULT_BUCKET_ORDER, ES_BUILTIN_TIE_BREAKER));
@@ -64,19 +64,19 @@ public class SubAggregationHelperTest {
@Test
public void buildTermsAggregation_adds_custom_order_from_constructor() {
String aggName = randomAlphabetic(10);
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);

TermsAggregationBuilder agg = underTestWithCustomsSubAggAndOrder.buildTermsAggregation(aggName, topAggregation, null);

assertThat(agg.getName()).isEqualTo(aggName);
assertThat(agg.field()).isEqualTo(topAggregation.getFieldName());
assertThat(agg.field()).isEqualTo(topAggregation.getFilterScope().getFieldName());
assertThat(agg.order()).isEqualTo(BucketOrder.compound(customOrder, ES_BUILTIN_TIE_BREAKER));
}

@Test
public void buildTermsAggregation_adds_custom_sub_agg_from_constructor() {
String aggName = randomAlphabetic(10);
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);

Stream.of(
underTestWithCustomSubAgg,
@@ -85,7 +85,7 @@ public class SubAggregationHelperTest {
TermsAggregationBuilder agg = t.buildTermsAggregation(aggName, topAggregation, null);

assertThat(agg.getName()).isEqualTo(aggName);
assertThat(agg.field()).isEqualTo(topAggregation.getFieldName());
assertThat(agg.field()).isEqualTo(topAggregation.getFilterScope().getFieldName());
assertThat(agg.getSubAggregations()).hasSize(1);
assertThat(agg.getSubAggregations().iterator().next()).isSameAs(customSubAgg);
});
@@ -95,7 +95,7 @@ public class SubAggregationHelperTest {
public void buildTermsAggregation_adds_custom_size_if_TermTopAggregation_specifies_one() {
String aggName = randomAlphabetic(10);
int customSize = 1 + new Random().nextInt(400);
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);

Stream.of(
underTest,
@@ -105,7 +105,7 @@ public class SubAggregationHelperTest {
TermsAggregationBuilder agg = t.buildTermsAggregation(aggName, topAggregation, customSize);

assertThat(agg.getName()).isEqualTo(aggName);
assertThat(agg.field()).isEqualTo(topAggregation.getFieldName());
assertThat(agg.field()).isEqualTo(topAggregation.getFilterScope().getFieldName());
assertThat(agg.size()).isEqualTo(customSize);
});
}
@@ -113,7 +113,7 @@ public class SubAggregationHelperTest {
@Test
public void buildSelectedItemsAggregation_returns_empty_if_no_selected_item() {
String aggName = randomAlphabetic(10);
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);

Stream.of(
underTest,
@@ -125,21 +125,21 @@ public class SubAggregationHelperTest {
@Test
public void buildSelectedItemsAggregation_does_not_add_custom_order_from_constructor() {
String aggName = randomAlphabetic(10);
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
String[] selected = randomNonEmptySelected();

TermsAggregationBuilder agg = underTestWithCustomsSubAggAndOrder.buildSelectedItemsAggregation(aggName, topAggregation, selected)
.get();

assertThat(agg.getName()).isEqualTo(aggName + "_selected");
assertThat(agg.field()).isEqualTo(topAggregation.getFieldName());
assertThat(agg.field()).isEqualTo(topAggregation.getFilterScope().getFieldName());
assertThat(agg.order()).isEqualTo(BucketOrder.compound(SQ_DEFAULT_BUCKET_ORDER, ES_BUILTIN_TIE_BREAKER));
}

@Test
public void buildSelectedItemsAggregation_adds_custom_sub_agg_from_constructor() {
String aggName = randomAlphabetic(10);
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
String[] selected = randomNonEmptySelected();

Stream.of(
@@ -149,7 +149,7 @@ public class SubAggregationHelperTest {
TermsAggregationBuilder agg = t.buildSelectedItemsAggregation(aggName, topAggregation, selected).get();

assertThat(agg.getName()).isEqualTo(aggName + "_selected");
assertThat(agg.field()).isEqualTo(topAggregation.getFieldName());
assertThat(agg.field()).isEqualTo(topAggregation.getFilterScope().getFieldName());
assertThat(agg.getSubAggregations()).hasSize(1);
assertThat(agg.getSubAggregations().iterator().next()).isSameAs(customSubAgg);
});

+ 0
- 89
server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/TermTopAggregationDefTest.java View File

@@ -1,89 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.es.searchrequest;

import java.util.Random;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;

public class TermTopAggregationDefTest {
private static final Random RANDOM = new Random();

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void fieldName_cannot_be_null() {
boolean sticky = RANDOM.nextBoolean();
int maxTerms = 10;

expectedException.expect(NullPointerException.class);
expectedException.expectMessage("fieldName can't be null");

new TermTopAggregationDef(null, sticky, maxTerms);
}

@Test
public void maxTerms_can_be_null() {
String fieldName = randomAlphabetic(12);
boolean sticky = RANDOM.nextBoolean();

TermTopAggregationDef underTest = new TermTopAggregationDef(fieldName, sticky, null);
assertThat(underTest.getMaxTerms()).isEmpty();
}

@Test
public void maxTerms_can_be_0() {
String fieldName = randomAlphabetic(12);
boolean sticky = RANDOM.nextBoolean();

TermTopAggregationDef underTest = new TermTopAggregationDef(fieldName, sticky, 0);
assertThat(underTest.getMaxTerms()).hasValue(0);
}

@Test
public void maxTerms_cant_be_less_than_0() {
String fieldName = randomAlphabetic(12);
boolean sticky = RANDOM.nextBoolean();
int negativeNumber = -1 - RANDOM.nextInt(200);

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("maxTerms can't be < 0");

new TermTopAggregationDef(fieldName, sticky, negativeNumber);
}

@Test
public void getters() {
String fieldName = randomAlphabetic(12);
boolean sticky = RANDOM.nextBoolean();
int maxTerms = RANDOM.nextInt(299);
TermTopAggregationDef underTest = new TermTopAggregationDef(fieldName, sticky, maxTerms);

assertThat(underTest.getFieldName()).isEqualTo(fieldName);
assertThat(underTest.isSticky()).isEqualTo(sticky);
assertThat(underTest.getMaxTerms()).hasValue(maxTerms);
}

}

+ 17
- 17
server/sonar-server-common/src/test/java/org/sonar/server/es/searchrequest/TopAggregationHelperTest.java View File

@@ -52,7 +52,7 @@ public class TopAggregationHelperTest {
@Test
public void buildTopAggregation_fails_with_ISE_if_no_subaggregation_added_by_lambda() {
String aggregationName = "name";
TopAggregationDefinition topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);

assertThatThrownBy(() -> underTest.buildTopAggregation(aggregationName, topAggregation, NO_EXTRA_FILTER, NO_OTHER_SUBAGGREGATION))
.isInstanceOf(IllegalStateException.class)
@@ -61,7 +61,7 @@ public class TopAggregationHelperTest {

@Test
public void buildTopAggregation_adds_subAggregation_from_lambda_parameter() {
TopAggregationDefinition topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
AggregationBuilder[] subAggs = IntStream.range(0, 1 + new Random().nextInt(12))
.mapToObj(i -> AggregationBuilders.min("subAgg_" + i))
.toArray(AggregationBuilder[]::new);
@@ -77,8 +77,8 @@ public class TopAggregationHelperTest {

@Test
public void buildTopAggregation_adds_filter_from_FiltersComputer_for_TopAggregation() {
TopAggregationDefinition topAggregation = new TopAggregationDef("bar", false);
TopAggregationDefinition otherTopAggregation = new TopAggregationDef("acme", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
SimpleFieldTopAggregationDefinition otherTopAggregation = new SimpleFieldTopAggregationDefinition("acme", false);
BoolQueryBuilder computerFilter = boolQuery();
BoolQueryBuilder otherFilter = boolQuery();
when(filtersComputer.getTopAggregationFilter(topAggregation)).thenReturn(Optional.of(computerFilter));
@@ -95,8 +95,8 @@ public class TopAggregationHelperTest {

@Test
public void buildTopAggregation_has_empty_filter_when_FiltersComputer_returns_empty_for_TopAggregation() {
TopAggregationDefinition topAggregation = new TopAggregationDef("bar", false);
TopAggregationDefinition otherTopAggregation = new TopAggregationDef("acme", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
SimpleFieldTopAggregationDefinition otherTopAggregation = new SimpleFieldTopAggregationDefinition("acme", false);
BoolQueryBuilder otherFilter = boolQuery();
when(filtersComputer.getTopAggregationFilter(topAggregation)).thenReturn(Optional.empty());
when(filtersComputer.getTopAggregationFilter(otherTopAggregation)).thenReturn(Optional.of(otherFilter));
@@ -113,8 +113,8 @@ public class TopAggregationHelperTest {
@Test
public void buildTopAggregation_adds_filter_from_FiltersComputer_for_TopAggregation_and_extra_one() {
String topAggregationName = randomAlphabetic(10);
TopAggregationDefinition topAggregation = new TopAggregationDef("bar", false);
TopAggregationDefinition otherTopAggregation = new TopAggregationDef("acme", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
SimpleFieldTopAggregationDefinition otherTopAggregation = new SimpleFieldTopAggregationDefinition("acme", false);
BoolQueryBuilder computerFilter = boolQuery();
BoolQueryBuilder otherFilter = boolQuery();
BoolQueryBuilder extraFilter = boolQuery();
@@ -132,7 +132,7 @@ public class TopAggregationHelperTest {

@Test
public void buildTopAggregation_does_not_add_subaggregation_from_subAggregationHelper() {
TopAggregationDefinition topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
when(filtersComputer.getTopAggregationFilter(topAggregation)).thenReturn(Optional.empty());
MinAggregationBuilder subAggregation = AggregationBuilders.min("donut");
String topAggregationName = randomAlphabetic(10);
@@ -145,7 +145,7 @@ public class TopAggregationHelperTest {
@Test
public void buildTermTopAggregation_adds_term_subaggregation_from_subAggregationHelper() {
String topAggregationName = randomAlphabetic(10);
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
TermsAggregationBuilder termSubAgg = AggregationBuilders.terms("foo");
when(subAggregationHelper.buildTermsAggregation(topAggregationName, topAggregation, null)).thenReturn(termSubAgg);

@@ -160,7 +160,7 @@ public class TopAggregationHelperTest {

@Test
public void buildTermTopAggregation_adds_subAggregation_from_lambda_parameter() {
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
AggregationBuilder[] subAggs = IntStream.range(0, 1 + new Random().nextInt(12))
.mapToObj(i -> AggregationBuilders.min("subAgg_" + i))
.toArray(AggregationBuilder[]::new);
@@ -180,8 +180,8 @@ public class TopAggregationHelperTest {

@Test
public void buildTermTopAggregation_adds_filter_from_FiltersComputer_for_TopAggregation() {
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
TopAggregationDef otherTopAggregation = new TopAggregationDef("acme", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
SimpleFieldTopAggregationDefinition otherTopAggregation = new SimpleFieldTopAggregationDefinition("acme", false);
BoolQueryBuilder computerFilter = boolQuery();
BoolQueryBuilder otherFilter = boolQuery();
when(filtersComputer.getTopAggregationFilter(topAggregation)).thenReturn(Optional.of(computerFilter));
@@ -200,8 +200,8 @@ public class TopAggregationHelperTest {

@Test
public void buildTermTopAggregation_has_empty_filter_when_FiltersComputer_returns_empty_for_TopAggregation() {
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
TopAggregationDef otherTopAggregation = new TopAggregationDef("acme", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
SimpleFieldTopAggregationDefinition otherTopAggregation = new SimpleFieldTopAggregationDefinition("acme", false);
BoolQueryBuilder otherFilter = boolQuery();
when(filtersComputer.getTopAggregationFilter(topAggregation)).thenReturn(Optional.empty());
when(filtersComputer.getTopAggregationFilter(otherTopAggregation)).thenReturn(Optional.of(otherFilter));
@@ -220,8 +220,8 @@ public class TopAggregationHelperTest {
@Test
public void buildTermTopAggregation_adds_filter_from_FiltersComputer_for_TopAggregation_and_extra_one() {
String topAggregationName = randomAlphabetic(10);
TopAggregationDef topAggregation = new TopAggregationDef("bar", false);
TopAggregationDef otherTopAggregation = new TopAggregationDef("acme", false);
SimpleFieldTopAggregationDefinition topAggregation = new SimpleFieldTopAggregationDefinition("bar", false);
SimpleFieldTopAggregationDefinition otherTopAggregation = new SimpleFieldTopAggregationDefinition("acme", false);
BoolQueryBuilder computerFilter = boolQuery();
BoolQueryBuilder otherFilter = boolQuery();
BoolQueryBuilder extraFilter = boolQuery();

+ 72
- 56
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java View File

@@ -80,9 +80,10 @@ import org.sonar.server.es.SearchOptions;
import org.sonar.server.es.Sorting;
import org.sonar.server.es.searchrequest.RequestFiltersComputer;
import org.sonar.server.es.searchrequest.RequestFiltersComputer.AllFilters;
import org.sonar.server.es.searchrequest.SimpleFieldTopAggregationDefinition;
import org.sonar.server.es.searchrequest.SubAggregationHelper;
import org.sonar.server.es.searchrequest.TopAggregationDef;
import org.sonar.server.es.searchrequest.TopAggregationDefinition;
import org.sonar.server.es.searchrequest.TopAggregationDefinition.SimpleFieldFilterScope;
import org.sonar.server.es.searchrequest.TopAggregationHelper;
import org.sonar.server.issue.index.IssueQuery.PeriodStart;
import org.sonar.server.permission.index.AuthorizationDoc;
@@ -107,6 +108,8 @@ import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.server.es.BaseDoc.epochMillisToEpochSeconds;
import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
import static org.sonar.server.es.searchrequest.TopAggregationDefinition.NON_STICKY;
import static org.sonar.server.es.searchrequest.TopAggregationDefinition.STICKY;
import static org.sonar.server.es.searchrequest.TopAggregationHelper.NO_EXTRA_FILTER;
import static org.sonar.server.es.searchrequest.TopAggregationHelper.NO_OTHER_SUBAGGREGATION;
import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNED_TO_ME;
@@ -217,10 +220,8 @@ public class IssueIndex {
.filter(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_REVIEWED))
.filter(termQuery(FIELD_ISSUE_RESOLUTION, Issue.RESOLUTION_FIXED));

private static final boolean STICKY = true;
private static final boolean NON_STICKY = false;
private static final Object[] NO_SELECTED_VALUES = {0};
private static final TopAggregationDefinition EFFORT_TOP_AGGREGATION = new TopAggregationDef(FIELD_ISSUE_EFFORT, NON_STICKY);
private static final SimpleFieldTopAggregationDefinition EFFORT_TOP_AGGREGATION = new SimpleFieldTopAggregationDefinition(FIELD_ISSUE_EFFORT, NON_STICKY);

public enum Facet {
SEVERITIES(PARAM_SEVERITIES, FIELD_ISSUE_SEVERITY, STICKY, Severity.ALL.size()),
@@ -246,18 +247,18 @@ public class IssueIndex {
SONARSOURCE_SECURITY(PARAM_SONARSOURCE_SECURITY, FIELD_ISSUE_SQ_SECURITY_CATEGORY, STICKY, DEFAULT_FACET_SIZE);

private final String name;
private final TopAggregationDefinition topAggregation;
private final SimpleFieldTopAggregationDefinition topAggregation;
private final Integer numberOfTerms;

Facet(String name, String fieldName, boolean sticky, int numberOfTerms) {
this.name = name;
this.topAggregation = new TopAggregationDef(fieldName, sticky);
this.topAggregation = new SimpleFieldTopAggregationDefinition(fieldName, sticky);
this.numberOfTerms = numberOfTerms;
}

Facet(String name, String fieldName, boolean sticky) {
this.name = name;
this.topAggregation = new TopAggregationDef(fieldName, sticky);
this.topAggregation = new SimpleFieldTopAggregationDefinition(fieldName, sticky);
this.numberOfTerms = null;
}

@@ -266,10 +267,14 @@ public class IssueIndex {
}

public String getFieldName() {
return topAggregation.getFieldName();
return topAggregation.getFilterScope().getFieldName();
}

public TopAggregationDefinition getTopAggregationDef() {
public TopAggregationDefinition.FilterScope getFilterScope() {
return topAggregation.getFilterScope();
}

public SimpleFieldTopAggregationDefinition getTopAggregationDef() {
return topAggregation;
}

@@ -389,41 +394,54 @@ public class IssueIndex {

private AllFilters createAllFilters(IssueQuery query) {
AllFilters filters = RequestFiltersComputer.newAllFilters();
filters.addFilter("__indexType", FIELD_INDEX_TYPE, termQuery(FIELD_INDEX_TYPE, TYPE_ISSUE.getName()));
filters.addFilter("__authorization", "parent", createAuthorizationFilter());
filters.addFilter("__indexType", new SimpleFieldFilterScope(FIELD_INDEX_TYPE), termQuery(FIELD_INDEX_TYPE, TYPE_ISSUE.getName()));
filters.addFilter("__authorization", new SimpleFieldFilterScope("parent"), createAuthorizationFilter());

// Issue is assigned Filter
if (BooleanUtils.isTrue(query.assigned())) {
filters.addFilter(IS_ASSIGNED_FILTER, FIELD_ISSUE_ASSIGNEE_UUID, existsQuery(FIELD_ISSUE_ASSIGNEE_UUID));
filters.addFilter(IS_ASSIGNED_FILTER, Facet.ASSIGNEES.getFilterScope(), existsQuery(FIELD_ISSUE_ASSIGNEE_UUID));
} else if (BooleanUtils.isFalse(query.assigned())) {
filters.addFilter(IS_ASSIGNED_FILTER, FIELD_ISSUE_ASSIGNEE_UUID, boolQuery().mustNot(existsQuery(FIELD_ISSUE_ASSIGNEE_UUID)));
filters.addFilter(IS_ASSIGNED_FILTER, ASSIGNEES.getFilterScope(), boolQuery().mustNot(existsQuery(FIELD_ISSUE_ASSIGNEE_UUID)));
}

// Issue is Resolved Filter
if (BooleanUtils.isTrue(query.resolved())) {
filters.addFilter("__isResolved", FIELD_ISSUE_RESOLUTION, existsQuery(FIELD_ISSUE_RESOLUTION));
filters.addFilter("__isResolved", Facet.RESOLUTIONS.getFilterScope(), existsQuery(FIELD_ISSUE_RESOLUTION));
} else if (BooleanUtils.isFalse(query.resolved())) {
filters.addFilter("__isResolved", FIELD_ISSUE_RESOLUTION, boolQuery().mustNot(existsQuery(FIELD_ISSUE_RESOLUTION)));
filters.addFilter("__isResolved", Facet.RESOLUTIONS.getFilterScope(), boolQuery().mustNot(existsQuery(FIELD_ISSUE_RESOLUTION)));
}

// Field Filters
filters.addFilter(FIELD_ISSUE_KEY, createTermsFilter(FIELD_ISSUE_KEY, query.issueKeys()));
filters.addFilter(FIELD_ISSUE_ASSIGNEE_UUID, createTermsFilter(FIELD_ISSUE_ASSIGNEE_UUID, query.assignees()));
filters.addFilter(FIELD_ISSUE_LANGUAGE, createTermsFilter(FIELD_ISSUE_LANGUAGE, query.languages()));
filters.addFilter(FIELD_ISSUE_TAGS, createTermsFilter(FIELD_ISSUE_TAGS, query.tags()));
filters.addFilter(FIELD_ISSUE_TYPE, createTermsFilter(FIELD_ISSUE_TYPE, query.types()));
filters.addFilter(FIELD_ISSUE_RESOLUTION, createTermsFilter(FIELD_ISSUE_RESOLUTION, query.resolutions()));
filters.addFilter(FIELD_ISSUE_AUTHOR_LOGIN, createTermsFilter(FIELD_ISSUE_AUTHOR_LOGIN, query.authors()));
filters.addFilter(FIELD_ISSUE_RULE_ID, createTermsFilter(
FIELD_ISSUE_RULE_ID,
query.rules().stream().map(IssueDoc::formatRuleId).collect(toList())));
filters.addFilter(FIELD_ISSUE_STATUS, createTermsFilter(FIELD_ISSUE_STATUS, query.statuses()));
filters.addFilter(FIELD_ISSUE_ORGANIZATION_UUID, createTermFilter(FIELD_ISSUE_ORGANIZATION_UUID, query.organizationUuid()));
filters.addFilter(FIELD_ISSUE_OWASP_TOP_10, createTermsFilter(FIELD_ISSUE_OWASP_TOP_10, query.owaspTop10()));
filters.addFilter(FIELD_ISSUE_SANS_TOP_25, createTermsFilter(FIELD_ISSUE_SANS_TOP_25, query.sansTop25()));
filters.addFilter(FIELD_ISSUE_CWE, createTermsFilter(FIELD_ISSUE_CWE, query.cwe()));
filters.addFilter(FIELD_ISSUE_KEY, new SimpleFieldFilterScope(FIELD_ISSUE_KEY), createTermsFilter(FIELD_ISSUE_KEY, query.issueKeys()));
filters.addFilter(FIELD_ISSUE_ASSIGNEE_UUID, ASSIGNEES.getFilterScope(), createTermsFilter(FIELD_ISSUE_ASSIGNEE_UUID, query.assignees()));
filters.addFilter(FIELD_ISSUE_LANGUAGE, LANGUAGES.getFilterScope(), createTermsFilter(FIELD_ISSUE_LANGUAGE, query.languages()));
filters.addFilter(FIELD_ISSUE_TAGS, TAGS.getFilterScope(), createTermsFilter(FIELD_ISSUE_TAGS, query.tags()));
filters.addFilter(FIELD_ISSUE_TYPE, TYPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_TYPE, query.types()));
filters.addFilter(
FIELD_ISSUE_RESOLUTION, RESOLUTIONS.getFilterScope(),
createTermsFilter(FIELD_ISSUE_RESOLUTION, query.resolutions()));
filters.addFilter(
FIELD_ISSUE_AUTHOR_LOGIN, AUTHOR.getFilterScope(),
createTermsFilter(FIELD_ISSUE_AUTHOR_LOGIN, query.authors()));
filters.addFilter(
FIELD_ISSUE_RULE_ID, RULES.getFilterScope(), createTermsFilter(
FIELD_ISSUE_RULE_ID,
query.rules().stream().map(IssueDoc::formatRuleId).collect(toList())));
filters.addFilter(FIELD_ISSUE_STATUS, STATUSES.getFilterScope(), createTermsFilter(FIELD_ISSUE_STATUS, query.statuses()));
filters.addFilter(
FIELD_ISSUE_ORGANIZATION_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_ORGANIZATION_UUID),
createTermFilter(FIELD_ISSUE_ORGANIZATION_UUID, query.organizationUuid()));
filters.addFilter(
FIELD_ISSUE_OWASP_TOP_10, OWASP_TOP_10.getFilterScope(),
createTermsFilter(FIELD_ISSUE_OWASP_TOP_10, query.owaspTop10()));
filters.addFilter(
FIELD_ISSUE_SANS_TOP_25, SANS_TOP_25.getFilterScope(),
createTermsFilter(FIELD_ISSUE_SANS_TOP_25, query.sansTop25()));
filters.addFilter(FIELD_ISSUE_CWE, CWE.getFilterScope(), createTermsFilter(FIELD_ISSUE_CWE, query.cwe()));
addSeverityFilter(query, filters);
filters.addFilter(FIELD_ISSUE_SQ_SECURITY_CATEGORY, createTermsFilter(FIELD_ISSUE_SQ_SECURITY_CATEGORY, query.sonarsourceSecurity()));
filters.addFilter(
FIELD_ISSUE_SQ_SECURITY_CATEGORY, SONARSOURCE_SECURITY.getFilterScope(),
createTermsFilter(FIELD_ISSUE_SQ_SECURITY_CATEGORY, query.sonarsourceSecurity()));

addComponentRelatedFilters(query, filters);
addDatesFilter(filters, query);
@@ -434,10 +452,13 @@ public class IssueIndex {
private static void addSeverityFilter(IssueQuery query, AllFilters allFilters) {
QueryBuilder severityFieldFilter = createTermsFilter(FIELD_ISSUE_SEVERITY, query.severities());
if (severityFieldFilter != null) {
allFilters.addFilter(FIELD_ISSUE_SEVERITY, boolQuery()
.must(severityFieldFilter)
// Ignore severity of Security HotSpots
.mustNot(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())));
allFilters.addFilter(
FIELD_ISSUE_SEVERITY,
SEVERITIES.getFilterScope(),
boolQuery()
.must(severityFieldFilter)
// Ignore severity of Security HotSpots
.mustNot(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())));
}
}

@@ -455,23 +476,22 @@ public class IssueIndex {
QueryBuilder fileFilter = createTermsFilter(FIELD_ISSUE_COMPONENT_UUID, query.fileUuids());

if (BooleanUtils.isTrue(query.onComponentOnly())) {
filters.addFilter(FIELD_ISSUE_COMPONENT_UUID, componentFilter);
filters.addFilter(FIELD_ISSUE_COMPONENT_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_COMPONENT_UUID), componentFilter);
} else {
filters.addFilter(
FIELD_ISSUE_PROJECT_UUID,
FIELD_ISSUE_PROJECT_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_PROJECT_UUID),
createTermsFilter(FIELD_ISSUE_PROJECT_UUID, query.projectUuids()));
filters.addFilter(
"__module",
FIELD_ISSUE_MODULE_PATH,
"__module", new SimpleFieldFilterScope(FIELD_ISSUE_MODULE_PATH),
createTermsFilter(FIELD_ISSUE_MODULE_PATH, query.moduleRootUuids()));
filters.addFilter(
FIELD_ISSUE_MODULE_UUID,
FIELD_ISSUE_MODULE_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_MODULE_UUID),
createTermsFilter(FIELD_ISSUE_MODULE_UUID, query.moduleUuids()));
filters.addFilter(
FIELD_ISSUE_DIRECTORY_PATH,
FIELD_ISSUE_DIRECTORY_PATH, new SimpleFieldFilterScope(FIELD_ISSUE_DIRECTORY_PATH),
createTermsFilter(FIELD_ISSUE_DIRECTORY_PATH, query.directories()));
filters.addFilter(
FIELD_ISSUE_COMPONENT_UUID,
FIELD_ISSUE_COMPONENT_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_COMPONENT_UUID),
fileFilter == null ? componentFilter : fileFilter);
}
}
@@ -481,11 +501,10 @@ public class IssueIndex {
return;
}
allFilters.addFilter(
"__is_main_branch",
FIELD_ISSUE_IS_MAIN_BRANCH,
"__is_main_branch", new SimpleFieldFilterScope(FIELD_ISSUE_IS_MAIN_BRANCH),
createTermFilter(FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(query.isMainBranch())));
allFilters.addFilter(
FIELD_ISSUE_BRANCH_UUID,
FIELD_ISSUE_BRANCH_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_BRANCH_UUID),
createTermFilter(FIELD_ISSUE_BRANCH_UUID, query.branchUuid()));
}

@@ -497,10 +516,10 @@ public class IssueIndex {
String branchUuid = query.branchUuid();
boolean onApplicationBranch = branchUuid != null && !viewUuids.isEmpty();
if (onApplicationBranch) {
allFilters.addFilter("__view", createViewFilter(singletonList(query.branchUuid())));
allFilters.addFilter("__view", new SimpleFieldFilterScope("view"), createViewFilter(singletonList(query.branchUuid())));
} else {
allFilters.addFilter("__is_main_branch", createTermFilter(FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(true)));
allFilters.addFilter("__view", createViewFilter(viewUuids));
allFilters.addFilter("__is_main_branch", new SimpleFieldFilterScope(FIELD_ISSUE_IS_MAIN_BRANCH), createTermFilter(FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(true)));
allFilters.addFilter("__view", new SimpleFieldFilterScope("view"), createViewFilter(viewUuids));
}
}

@@ -525,7 +544,7 @@ public class IssueIndex {

private static RequestFiltersComputer newFilterComputer(SearchOptions options, AllFilters allFilters) {
Collection<String> facetNames = options.getFacets();
Set<TopAggregationDefinition> facets = Stream.concat(
Set<TopAggregationDefinition<?>> facets = Stream.concat(
Stream.of(EFFORT_TOP_AGGREGATION),
facetNames.stream()
.map(FACETS_BY_NAME::get)
@@ -589,16 +608,14 @@ public class IssueIndex {

if (createdAfter != null) {
filters.addFilter(
"__createdAfter",
FIELD_ISSUE_FUNC_CREATED_AT,
"__createdAfter", CREATED_AT.getFilterScope(),
QueryBuilders
.rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT)
.from(BaseDoc.dateToEpochSeconds(createdAfter.date()), createdAfter.inclusive()));
}
if (createdBefore != null) {
filters.addFilter(
"__createdBefore",
FIELD_ISSUE_FUNC_CREATED_AT,
"__createdBefore", CREATED_AT.getFilterScope(),
QueryBuilders
.rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT)
.lt(BaseDoc.dateToEpochSeconds(createdBefore)));
@@ -606,8 +623,7 @@ public class IssueIndex {
Date createdAt = query.createdAt();
if (createdAt != null) {
filters.addFilter(
"__createdAt",
FIELD_ISSUE_FUNC_CREATED_AT,
"__createdAt", CREATED_AT.getFilterScope(),
termQuery(FIELD_ISSUE_FUNC_CREATED_AT, BaseDoc.dateToEpochSeconds(createdAt)));
}
}
@@ -618,7 +634,7 @@ public class IssueIndex {
createdAfterByProjectUuids.forEach((projectUuid, createdAfterDate) -> boolQueryBuilder.should(boolQuery()
.filter(termQuery(FIELD_ISSUE_PROJECT_UUID, projectUuid))
.filter(rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT).from(BaseDoc.dateToEpochSeconds(createdAfterDate.date()), createdAfterDate.inclusive()))));
allFilters.addFilter("createdAfterByProjectUuids", boolQueryBuilder);
allFilters.addFilter("createdAfterByProjectUuids", new SimpleFieldFilterScope("TODO::???"), boolQueryBuilder);
}

private void validateCreationDateBounds(@Nullable Date createdBefore, @Nullable Date createdAfter) {

Loading…
Cancel
Save