This is an automated email from the ASF dual-hosted git repository.
fortino pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new 0440e096dc OAK-10553: improved elastic statistical facets (#1217)
0440e096dc is described below
commit 0440e096dc7a460d206c77a5f178796db31a0ef4
Author: Fabrizio Fortino <[email protected]>
AuthorDate: Fri Nov 17 16:23:39 2023 +0100
OAK-10553: improved elastic statistical facets (#1217)
* OAK-10553: improved elastic statical facets (no need to perform a full
index traverse)
* OAK-10553: improved FacetCommonTest
* OAK-10553: minor improvements
* OAK-10553: revise interrupted exception handling
* OAK-10553: improved statistical facets accuracy
* OAK-10553: better message in case facets time out
---
.../index/elastic/ElasticIndexDefinition.java | 8 +
.../index/elastic/index/ElasticIndexWriter.java | 2 +-
.../index/elastic/query/ElasticRequestHandler.java | 124 ++++++------
.../query/async/ElasticResultRowAsyncIterator.java | 9 +-
.../query/async/facets/ElasticFacetProvider.java | 16 +-
.../facets/ElasticInsecureFacetAsyncProvider.java | 6 +-
.../facets/ElasticSecureFacetAsyncProvider.java | 25 ++-
.../ElasticStatisticalFacetAsyncProvider.java | 223 ++++++++++++---------
.../index/elastic/ElasticConnectionRule.java | 16 +-
.../plugins/index/elastic/ElasticFacetTest.java | 2 +-
.../oak/plugins/index/FacetCommonTest.java | 103 ++++++----
11 files changed, 312 insertions(+), 222 deletions(-)
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
index 9ad83c5191..82d0374fdd 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
@@ -75,6 +75,12 @@ public class ElasticIndexDefinition extends IndexDefinition {
public static final String FAIL_ON_ERROR = "failOnError";
public static final boolean FAIL_ON_ERROR_DEFAULT = true;
+ /**
+ * When 0, the index name gets dynamically generated by adding a random
suffix to the index name.
+ */
+ public static final String INDEX_NAME_SEED = "indexNameSeed";
+ public static final long INDEX_NAME_SEED_DEFAULT = 0L;
+
/**
* Hidden property for storing a seed value to be used as suffix in remote
index name.
*/
@@ -135,6 +141,7 @@ public class ElasticIndexDefinition extends IndexDefinition
{
public final Integer trackTotalHits;
public final String dynamicMapping;
public final boolean failOnError;
+ public final long indexNameSeed;
private final Map<String, List<PropertyDefinition>> propertiesByName;
private final List<PropertyDefinition> dynamicBoostProperties;
@@ -160,6 +167,7 @@ public class ElasticIndexDefinition extends IndexDefinition
{
this.failOnError = getOptionalValue(defn, FAIL_ON_ERROR,
Boolean.parseBoolean(System.getProperty(TYPE_ELASTICSEARCH +
"." + FAIL_ON_ERROR, Boolean.toString(FAIL_ON_ERROR_DEFAULT)))
);
+ this.indexNameSeed = getOptionalValue(defn, INDEX_NAME_SEED,
INDEX_NAME_SEED_DEFAULT);
this.similarityTagsFields = getOptionalValues(defn,
SIMILARITY_TAGS_FIELDS, Type.STRINGS, String.class,
SIMILARITY_TAGS_FIELDS_DEFAULT);
this.propertiesByName = getDefinedRules()
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
index 13968326f7..3bbd7d3daf 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
@@ -76,7 +76,7 @@ class ElasticIndexWriter implements
FulltextIndexWriter<ElasticDocument> {
// old index until the new one gets enabled) during incremental
reindexing
if (this.reindex) {
try {
- long seed = UUID.randomUUID().getMostSignificantBits();
+ long seed = indexDefinition.indexNameSeed == 0L ?
UUID.randomUUID().getMostSignificantBits() : indexDefinition.indexNameSeed;
// merge gets called on node store later in the indexing flow
definitionBuilder.setProperty(ElasticIndexDefinition.PROP_INDEX_NAME_SEED,
seed);
// let's store the current mapping version in the index
definition
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
index 3ec9e51a92..e4cd77b21d 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
@@ -63,6 +63,7 @@ import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticConnection;
import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
import
org.apache.jackrabbit.oak.plugins.index.elastic.ElasticPropertyDefinition;
import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.facets.ElasticFacetProvider;
@@ -150,70 +151,69 @@ public class ElasticRequestHandler {
}
public Query baseQuery() {
- return Query.of(fn -> {
- fn.bool(fnb -> {
+ return Query.of(q -> q.bool(baseQueryBuilder().build()));
+ }
- FullTextExpression ft = filter.getFullTextConstraint();
+ public BoolQuery.Builder baseQueryBuilder() {
+ BoolQuery.Builder bqb = new BoolQuery.Builder();
+ FullTextExpression ft = filter.getFullTextConstraint();
- if (ft != null) {
- fnb.must(fullTextQuery(ft, planResult));
- }
+ if (ft != null) {
+ bqb.must(fullTextQuery(ft, planResult));
+ }
- if (propertyRestrictionQuery != null) {
- if (propertyRestrictionQuery.startsWith("mlt?")) {
- List<PropertyDefinition> sp =
elasticIndexDefinition.getSimilarityProperties();
- String mltQueryString =
propertyRestrictionQuery.substring("mlt?".length());
- Map<String, String> mltParams =
MoreLikeThisHelperUtil.getParamMapFromMltQuery(mltQueryString);
- String queryNodePath =
mltParams.get(MoreLikeThisHelperUtil.MLT_STREAM_BODY);
-
- if (queryNodePath == null) {
- // TODO : See if we might want to support
like Text here (passed as null in
- // above constructors)
- // IT is not supported in our lucene
implementation.
- throw new IllegalArgumentException(
- "Missing required field
stream.body in MLT query: " + mltQueryString);
- }
- if (sp.isEmpty()) {
- // SimilarityImpl in oak-core sets
property restriction for sim search and the
- // query is something like
- //
mlt?mlt.fl=:path&mlt.mindf=0&stream.body=<path> . We need parse this query
- // string and turn into a query
- // elastic can understand.
- fnb.must(m ->
m.moreLikeThis(mltQuery(mltParams)));
- } else {
- fnb.must(m ->
m.bool(similarityQuery(queryNodePath, sp)));
- }
-
- // Add should clause to improve relevance
using similarity tags only when similarity is
- // enabled and there is at least one
similarity tag property
- if
(elasticIndexDefinition.areSimilarityTagsEnabled() &&
-
!elasticIndexDefinition.getSimilarityTagsProperties().isEmpty()) {
- // add should clause to improve relevance
using similarity tags
- fnb.should(s -> s
- .moreLikeThis(m -> m
-
.fields(ElasticIndexDefinition.SIMILARITY_TAGS)
- .like(l -> l.document(d ->
d.id(ElasticIndexUtils.idFromPath(queryNodePath))))
- .minTermFreq(1)
- .minDocFreq(1)
-
.boost(elasticIndexDefinition.getSimilarityTagsBoost())
- )
- );
- }
-
- } else {
- fnb.must(m -> m.queryString(qs ->
qs.query(propertyRestrictionQuery)));
- }
+ if (propertyRestrictionQuery != null) {
+ if (propertyRestrictionQuery.startsWith("mlt?")) {
+ List<PropertyDefinition> sp =
elasticIndexDefinition.getSimilarityProperties();
+ String mltQueryString =
propertyRestrictionQuery.substring("mlt?".length());
+ Map<String, String> mltParams =
MoreLikeThisHelperUtil.getParamMapFromMltQuery(mltQueryString);
+ String queryNodePath =
mltParams.get(MoreLikeThisHelperUtil.MLT_STREAM_BODY);
+
+ if (queryNodePath == null) {
+ // TODO : See if we might want to support like Text here
(passed as null in
+ // above constructors)
+ // IT is not supported in our lucene implementation.
+ throw new IllegalArgumentException(
+ "Missing required field stream.body in MLT query:
" + mltQueryString);
+ }
+ if (sp.isEmpty()) {
+ // SimilarityImpl in oak-core sets property restriction
for sim search and the
+ // query is something like
+ // mlt?mlt.fl=:path&mlt.mindf=0&stream.body=<path> . We
need parse this query
+ // string and turn into a query
+ // elastic can understand.
+ bqb.must(m -> m.moreLikeThis(mltQuery(mltParams)));
+ } else {
+ bqb.must(m -> m.bool(similarityQuery(queryNodePath, sp)));
+ }
- } else if
(planResult.evaluateNonFullTextConstraints()) {
- for (Query constraint :
nonFullTextConstraints(indexPlan, planResult)) {
- fnb.filter(constraint);
- }
- }
- return fnb;
- });
- return fn;
+ // Add should clause to improve relevance using similarity
tags only when similarity is
+ // enabled and there is at least one similarity tag property
+ if (elasticIndexDefinition.areSimilarityTagsEnabled() &&
+
!elasticIndexDefinition.getSimilarityTagsProperties().isEmpty()) {
+ // add should clause to improve relevance using similarity
tags
+ bqb.should(s -> s
+ .moreLikeThis(m -> m
+
.fields(ElasticIndexDefinition.SIMILARITY_TAGS)
+ .like(l -> l.document(d ->
d.id(ElasticIndexUtils.idFromPath(queryNodePath))))
+ .minTermFreq(1)
+ .minDocFreq(1)
+
.boost(elasticIndexDefinition.getSimilarityTagsBoost())
+ )
+ );
}
- );
+
+ } else {
+ bqb.must(m -> m.queryString(qs ->
qs.query(propertyRestrictionQuery)));
+ }
+
+ } else if (planResult.evaluateNonFullTextConstraints()) {
+ for (Query constraint : nonFullTextConstraints(indexPlan,
planResult)) {
+ bqb.filter(constraint);
+ }
+ }
+
+ return bqb;
}
public @NotNull List<SortOptions> baseSorts() {
@@ -271,10 +271,10 @@ public class ElasticRequestHandler {
return propertyRestrictionQuery != null &&
propertyRestrictionQuery.startsWith(SUGGEST_PREFIX);
}
- public ElasticFacetProvider getAsyncFacetProvider(ElasticResponseHandler
responseHandler) {
+ public ElasticFacetProvider getAsyncFacetProvider(ElasticConnection
connection, ElasticResponseHandler responseHandler) {
return requiresFacets()
- ?
ElasticFacetProvider.getProvider(planResult.indexDefinition.getSecureFacetConfiguration(),
this,
- responseHandler, filter::isAccessible)
+ ?
ElasticFacetProvider.getProvider(planResult.indexDefinition.getSecureFacetConfiguration(),
connection,
+ elasticIndexDefinition, this, responseHandler,
filter::isAccessible)
: null;
}
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
index 05fc293b6a..b350762fe8 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
@@ -95,7 +95,7 @@ public class ElasticResultRowAsyncIterator implements
ElasticQueryIterator, Elas
this.indexPlan = indexPlan;
this.rowInclusionPredicate = rowInclusionPredicate;
this.metricHandler = metricHandler;
- this.elasticFacetProvider =
elasticRequestHandler.getAsyncFacetProvider(elasticResponseHandler);
+ this.elasticFacetProvider =
elasticRequestHandler.getAsyncFacetProvider(indexNode.getConnection(),
elasticResponseHandler);
this.elasticQueryScanner = initScanner();
}
@@ -110,6 +110,7 @@ public class ElasticResultRowAsyncIterator implements
ElasticQueryIterator, Elas
try {
nextRow = queue.take();
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // restore interrupt
status
throw new IllegalStateException("Error reading next result
from Elastic", e);
}
}
@@ -155,6 +156,7 @@ public class ElasticResultRowAsyncIterator implements
ElasticQueryIterator, Elas
queue.put(new FulltextResultRow(path, searchHit.score() !=
null ? searchHit.score() : 0.0,
elasticResponseHandler.excerpts(searchHit),
elasticFacetProvider, null));
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // restore interrupt
status
throw new IllegalStateException("Error producing results into
the iterator queue", e);
}
}
@@ -165,6 +167,7 @@ public class ElasticResultRowAsyncIterator implements
ElasticQueryIterator, Elas
try {
queue.put(POISON_PILL);
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // restore interrupt status
throw new IllegalStateException("Error inserting poison pill into
the iterator queue", e);
}
}
@@ -173,8 +176,8 @@ public class ElasticResultRowAsyncIterator implements
ElasticQueryIterator, Elas
List<ElasticResponseListener> listeners = new ArrayList<>();
// TODO: we could avoid to register this listener when the client is
interested in facets only. It would save space and time
listeners.add(this);
- if (elasticFacetProvider != null) {
- listeners.add(elasticFacetProvider);
+ if (elasticFacetProvider != null && elasticFacetProvider instanceof
ElasticResponseListener) {
+ listeners.add((ElasticResponseListener) elasticFacetProvider);
}
return new ElasticQueryScanner(listeners);
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
index 9b1c3a5737..8b3058d4d7 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
@@ -16,6 +16,8 @@
*/
package org.apache.jackrabbit.oak.plugins.index.elastic.query.async.facets;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticConnection;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticRequestHandler;
import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticResponseHandler;
import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResponseListener;
@@ -25,13 +27,15 @@ import
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
import java.util.function.Predicate;
/**
- * Provider of facets through an {@link ElasticResponseListener}
+ * Provider of facets for Elasticsearch
*/
-public interface ElasticFacetProvider extends FulltextIndex.FacetProvider,
ElasticResponseListener {
+public interface ElasticFacetProvider extends FulltextIndex.FacetProvider {
/**
* Returns the appropriate provider based on the {@link
SecureFacetConfiguration}
* @param facetConfiguration the {@link SecureFacetConfiguration} to
extract facet options
+ * @param connection the {@link ElasticConnection} to perform requests
+ * @param indexDefinition the {@link ElasticIndexDefinition} to extract
index options
* @param requestHandler the {@link ElasticRequestHandler} to perform
actions at request time
* @param responseHandler the {@link ElasticResponseHandler} to decode
responses
* @param isAccessible a {@link Predicate} to check if a node is accessible
@@ -41,6 +45,8 @@ public interface ElasticFacetProvider extends
FulltextIndex.FacetProvider, Elast
*/
static ElasticFacetProvider getProvider(
SecureFacetConfiguration facetConfiguration,
+ ElasticConnection connection,
+ ElasticIndexDefinition indexDefinition,
ElasticRequestHandler requestHandler,
ElasticResponseHandler responseHandler,
Predicate<String> isAccessible
@@ -51,9 +57,9 @@ public interface ElasticFacetProvider extends
FulltextIndex.FacetProvider, Elast
facetProvider = new ElasticInsecureFacetAsyncProvider();
break;
case STATISTICAL:
- facetProvider = new ElasticStatisticalFacetAsyncProvider(
- requestHandler, responseHandler, isAccessible,
- facetConfiguration.getRandomSeed(),
facetConfiguration.getStatisticalFacetSampleSize()
+ facetProvider = new
ElasticStatisticalFacetAsyncProvider(connection, indexDefinition,
+ requestHandler, responseHandler, isAccessible,
facetConfiguration.getRandomSeed(),
+ facetConfiguration.getStatisticalFacetSampleSize()
);
break;
case SECURE:
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticInsecureFacetAsyncProvider.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticInsecureFacetAsyncProvider.java
index 50f1bd13c2..ef207e402e 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticInsecureFacetAsyncProvider.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticInsecureFacetAsyncProvider.java
@@ -45,8 +45,12 @@ class ElasticInsecureFacetAsyncProvider implements
ElasticFacetProvider, Elastic
public List<FulltextIndex.Facet> getFacets(int numberOfFacets, String
columnName) {
LOG.trace("Requested facets for {} - Latch count: {}", columnName,
latch.getCount());
try {
- latch.await(15, TimeUnit.SECONDS);
+ boolean completed = latch.await(15, TimeUnit.SECONDS);
+ if (!completed) {
+ throw new IllegalStateException("Timed out while waiting for
facets");
+ }
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // restore interrupt status
throw new IllegalStateException("Error while waiting for facets",
e);
}
LOG.trace("Reading facets for {} from aggregations {}", columnName,
aggregations);
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
index 2cc1b3e704..a02a6afcb1 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
@@ -41,15 +41,14 @@ import java.util.stream.Collectors;
*/
class ElasticSecureFacetAsyncProvider implements ElasticFacetProvider,
ElasticResponseListener.SearchHitListener {
- protected static final Logger LOG =
LoggerFactory.getLogger(ElasticSecureFacetAsyncProvider.class);
-
- protected final Set<String> facetFields;
- private final Map<String, Map<String, Integer>> facetsMap = new
ConcurrentHashMap<>();
- private Map<String, List<FulltextIndex.Facet>> facets;
- protected final ElasticResponseHandler elasticResponseHandler;
- protected final Predicate<String> isAccessible;
+ private static final Logger LOG =
LoggerFactory.getLogger(ElasticSecureFacetAsyncProvider.class);
+ private final Set<String> facetFields;
+ private final Map<String, Map<String, Integer>> accessibleFacetCounts =
new ConcurrentHashMap<>();
+ private final ElasticResponseHandler elasticResponseHandler;
+ private final Predicate<String> isAccessible;
private final CountDownLatch latch = new CountDownLatch(1);
+ private Map<String, List<FulltextIndex.Facet>> facets;
ElasticSecureFacetAsyncProvider(
ElasticRequestHandler elasticRequestHandler,
@@ -78,7 +77,7 @@ class ElasticSecureFacetAsyncProvider implements
ElasticFacetProvider, ElasticRe
for (String field: facetFields) {
JsonNode value = searchHit.source().get(field);
if (value != null) {
- facetsMap.compute(field, (column, facetValues) -> {
+ accessibleFacetCounts.compute(field, (column, facetValues)
-> {
if (facetValues == null) {
Map<String, Integer> values = new HashMap<>();
values.put(value.asText(), 1);
@@ -96,7 +95,7 @@ class ElasticSecureFacetAsyncProvider implements
ElasticFacetProvider, ElasticRe
@Override
public void endData() {
// create Facet objects, order by count (desc) and then by label (asc)
- facets = facetsMap.entrySet()
+ facets = accessibleFacetCounts.entrySet()
.stream()
.collect(Collectors.toMap
(Map.Entry::getKey, x -> x.getValue().entrySet()
@@ -112,7 +111,7 @@ class ElasticSecureFacetAsyncProvider implements
ElasticFacetProvider, ElasticRe
.collect(Collectors.toList())
)
);
- LOG.trace("End data {}", facetsMap);
+ LOG.trace("End data {}", facets);
latch.countDown();
}
@@ -120,8 +119,12 @@ class ElasticSecureFacetAsyncProvider implements
ElasticFacetProvider, ElasticRe
public List<FulltextIndex.Facet> getFacets(int numberOfFacets, String
columnName) {
LOG.trace("Requested facets for {} - Latch count: {}", columnName,
latch.getCount());
try {
- latch.await(15, TimeUnit.SECONDS);
+ boolean completed = latch.await(15, TimeUnit.SECONDS);
+ if (!completed) {
+ throw new IllegalStateException("Timed out while waiting for
facets");
+ }
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // restore interrupt status
throw new IllegalStateException("Error while waiting for facets",
e);
}
LOG.trace("Reading facets for {} from {}", columnName, facets);
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
index 4611062dc1..bc79385308 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
@@ -18,145 +18,178 @@ package
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.facets;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
+import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.Query;
+import co.elastic.clients.elasticsearch.core.SearchRequest;
+import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
+import co.elastic.clients.elasticsearch.core.search.SourceConfig;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticConnection;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticRequestHandler;
import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticResponseHandler;
-import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResponseListener;
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
- * An {@link ElasticSecureFacetAsyncProvider} extension that subscribes also
on Elastic Aggregation events.
+ * An {@link ElasticFacetProvider} extension that performs random sampling on
the result set to compute facets.
* SearchHit events are sampled and then used to adjust facets coming from
Aggregations in order to minimize
- * access checks. This provider could improve facets performance but only when
the result set is quite big.
+ * access checks. This provider could improve facets performance especially
when the result set is quite big.
*/
-public class ElasticStatisticalFacetAsyncProvider extends
ElasticSecureFacetAsyncProvider
- implements ElasticResponseListener.AggregationListener {
+public class ElasticStatisticalFacetAsyncProvider implements
ElasticFacetProvider {
- private final int sampleSize;
+ private static final Logger LOG =
LoggerFactory.getLogger(ElasticStatisticalFacetAsyncProvider.class);
+
+ private final ElasticResponseHandler elasticResponseHandler;
+ private final Predicate<String> isAccessible;
+ private final Set<String> facetFields;
+ private final Map<String, List<FulltextIndex.Facet>> allFacets = new
HashMap<>();
+ private final Map<String, Map<String, Integer>> accessibleFacetCounts =
new ConcurrentHashMap<>();
+ private Map<String, List<FulltextIndex.Facet>> facets;
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private int sampled;
private long totalHits;
- private final Random rGen;
- private int sampled = 0;
- private int seen = 0;
- private long accessibleCount = 0;
+ ElasticStatisticalFacetAsyncProvider(ElasticConnection connection,
ElasticIndexDefinition indexDefinition,
+ ElasticRequestHandler
elasticRequestHandler, ElasticResponseHandler elasticResponseHandler,
+ Predicate<String> isAccessible, long
randomSeed, int sampleSize) {
- private final Map<String, List<FulltextIndex.Facet>> facetMap = new
HashMap<>();
+ this.elasticResponseHandler = elasticResponseHandler;
+ this.isAccessible = isAccessible;
+ this.facetFields =
elasticRequestHandler.facetFields().collect(Collectors.toSet());
- private final CountDownLatch latch = new CountDownLatch(1);
+ BoolQuery.Builder builder = elasticRequestHandler.baseQueryBuilder();
+ builder.should(sb -> sb.functionScore(fsb ->
+ fsb.functions(f -> f.randomScore(rsb -> rsb.seed("" +
randomSeed).field(FieldNames.PATH)))
+ ));
- ElasticStatisticalFacetAsyncProvider(ElasticRequestHandler
elasticRequestHandler,
- ElasticResponseHandler
elasticResponseHandler,
- Predicate<String> isAccessible,
- long randomSeed, int sampleSize) {
- super(elasticRequestHandler, elasticResponseHandler, isAccessible);
- this.sampleSize = sampleSize;
- this.rGen = new Random(randomSeed);
- }
+ SearchRequest searchRequest = SearchRequest.of(srb ->
srb.index(indexDefinition.getIndexAlias())
+ .trackTotalHits(thb -> thb.enabled(true))
+ .source(SourceConfig.of(scf -> scf.filter(ff ->
ff.includes(FieldNames.PATH).includes(new ArrayList<>(facetFields)))))
+ .query(Query.of(qb -> qb.bool(builder.build())))
+ .aggregations(elasticRequestHandler.aggregations())
+ .size(sampleSize)
+ );
- @Override
- public void startData(long totalHits) {
- this.totalHits = totalHits;
+ LOG.trace("Kicking search query with random sampling {}",
searchRequest);
+ CompletableFuture<SearchResponse<ObjectNode>> searchFuture =
+ connection.getAsyncClient().search(searchRequest,
ObjectNode.class);
+
+ searchFuture.whenCompleteAsync((searchResponse, throwable) -> {
+ try {
+ if (throwable != null) {
+ LOG.error("Error while retrieving sample documents",
throwable);
+ } else {
+ List<Hit<ObjectNode>> searchHits =
searchResponse.hits().hits();
+ this.sampled = searchHits != null ? searchHits.size() : 0;
+ if (sampled > 0) {
+ this.totalHits = searchResponse.hits().total().value();
+ processAggregations(searchResponse.aggregations());
+ searchResponse.hits().hits().forEach(this::processHit);
+ computeStatisticalFacets();
+ }
+ }
+ } finally {
+ latch.countDown();
+ }
+ });
}
@Override
- public void on(Hit<ObjectNode> searchHit) {
- if (totalHits < sampleSize) {
- super.on(searchHit);
- } else {
- if (sampleSize == sampled) {
- return;
+ public List<FulltextIndex.Facet> getFacets(int numberOfFacets, String
columnName) {
+ LOG.trace("Requested facets for {} - Latch count: {}", columnName,
latch.getCount());
+ try {
+ boolean completed = latch.await(15, TimeUnit.SECONDS);
+ if (!completed) {
+ throw new IllegalStateException("Timed out while waiting for
facets");
}
- int r = rGen.nextInt((int) (totalHits - seen)) + 1;
- seen++;
-
- if (r <= sampleSize - sampled) {
- sampled++;
- final String path = elasticResponseHandler.getPath(searchHit);
- if (path != null && isAccessible.test(path)) {
- accessibleCount++;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // restore interrupt status
+ throw new IllegalStateException("Error while waiting for facets",
e);
+ }
+ LOG.trace("Reading facets for {} from {}", columnName, facets);
+ return facets != null ?
facets.get(FulltextIndex.parseFacetField(columnName)) : null;
+ }
+
+ private void processHit(Hit<ObjectNode> searchHit) {
+ final String path = elasticResponseHandler.getPath(searchHit);
+ if (path != null && isAccessible.test(path)) {
+ for (String field : facetFields) {
+ JsonNode value = searchHit.source().get(field);
+ if (value != null) {
+ accessibleFacetCounts.compute(field, (column, facetValues)
-> {
+ if (facetValues == null) {
+ Map<String, Integer> values = new HashMap<>();
+ values.put(value.asText(), 1);
+ return values;
+ } else {
+ facetValues.merge(value.asText(), 1, Integer::sum);
+ return facetValues;
+ }
+ });
}
}
}
}
- @Override
- public void on(Map<String, Aggregate> aggregations) {
+ private void processAggregations(Map<String, Aggregate> aggregations) {
for (String field : facetFields) {
List<StringTermsBucket> buckets =
aggregations.get(field).sterms().buckets().array();
- facetMap.put(field, buckets.stream()
+ allFacets.put(field, buckets.stream()
.map(b -> new FulltextIndex.Facet(b.key().stringValue(),
(int) b.docCount()))
.collect(Collectors.toList())
);
}
}
- @Override
- public void endData() {
- if (totalHits < sampleSize) {
- super.endData();
- } else {
- for (String facet: facetMap.keySet()) {
- facetMap.compute(facet, (s, facets1) ->
updateLabelAndValueIfRequired(facets1));
- }
- latch.countDown();
- }
- }
-
- @Override
- public List<FulltextIndex.Facet> getFacets(int numberOfFacets, String
columnName) {
- if (totalHits < sampleSize) {
- return super.getFacets(numberOfFacets, columnName);
- } else {
- LOG.trace("Requested facets for {} - Latch count: {}", columnName,
latch.getCount());
- try {
- latch.await(15, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- throw new IllegalStateException("Error while waiting for
facets", e);
- }
- LOG.trace("Reading facets for {} from {}", columnName, facetMap);
- return facetMap.get(FulltextIndex.parseFacetField(columnName));
- }
- }
-
- private List<FulltextIndex.Facet>
updateLabelAndValueIfRequired(List<FulltextIndex.Facet> labelAndValues) {
- if (accessibleCount < sampleSize) {
- int numZeros = 0;
- List<FulltextIndex.Facet> newValues;
- {
- List<FulltextIndex.Facet> proportionedLVs = new LinkedList<>();
- for (FulltextIndex.Facet labelAndValue : labelAndValues) {
- long count = labelAndValue.getCount() * accessibleCount /
sampleSize;
- if (count == 0) {
- numZeros++;
+ private void computeStatisticalFacets() {
+ for (String facetKey : allFacets.keySet()) {
+ if (accessibleFacetCounts.containsKey(facetKey)) {
+ Map<String, Integer> accessibleFacet =
accessibleFacetCounts.get(facetKey);
+ List<FulltextIndex.Facet> uncheckedFacet =
allFacets.get(facetKey);
+ for (FulltextIndex.Facet facet : uncheckedFacet) {
+ if (accessibleFacet.containsKey(facet.getLabel())) {
+ double sampleProportion = (double)
accessibleFacet.get(facet.getLabel()) / sampled;
+ // returned count is the minimum between the
accessible count and the count computed from the sample
+ accessibleFacet.put(facet.getLabel(),
Math.min(facet.getCount(), (int) (sampleProportion * totalHits)));
}
- proportionedLVs.add(new
FulltextIndex.Facet(labelAndValue.getLabel(), Math.toIntExact(count)));
}
- labelAndValues = proportionedLVs;
}
- if (numZeros > 0) {
- newValues = new LinkedList<>();
- for (FulltextIndex.Facet lv : labelAndValues) {
- if (lv.getCount() > 0) {
- newValues.add(lv);
- }
- }
- } else {
- newValues = labelAndValues;
- }
- return newValues;
- } else {
- return labelAndValues;
}
+ // create Facet objects, order by count (desc) and then by label (asc)
+ facets = accessibleFacetCounts.entrySet()
+ .stream()
+ .collect(Collectors.toMap
+ (Map.Entry::getKey, x -> x.getValue().entrySet()
+ .stream()
+ .map(e -> new FulltextIndex.Facet(e.getKey(),
e.getValue()))
+ .sorted((f1, f2) -> {
+ int f1Count = f1.getCount();
+ int f2Count = f2.getCount();
+ if (f1Count == f2Count) {
+ return
f1.getLabel().compareTo(f2.getLabel());
+ } else return f2Count - f1Count;
+ })
+ .collect(Collectors.toList())
+ )
+ );
+ LOG.trace("Statistical facets {}", facets);
}
+
}
diff --git
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
index 9bb4cf022d..d47210d8f4 100644
---
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
+++
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
@@ -48,8 +48,16 @@ public class ElasticConnectionRule extends ExternalResource {
private ElasticConnectionModel elasticConnectionModel;
public ElasticConnectionRule(String elasticConnectionString) {
+ this(elasticConnectionString,
+ "elastic_test_" +
+ RandomStringUtils.random(5, true, false).toLowerCase()
+
+ System.currentTimeMillis()
+ );
+ }
+
+ public ElasticConnectionRule(String elasticConnectionString, String
indexPrefix) {
this.elasticConnectionString = elasticConnectionString;
- indexPrefix = "elastic_test_" + RandomStringUtils.random(5, true,
false).toLowerCase();
+ this.indexPrefix = indexPrefix;
}
public ElasticsearchContainer elastic;
@@ -105,7 +113,7 @@ public class ElasticConnectionRule extends ExternalResource
{
elasticConnectionModel.elasticPort = port;
elasticConnectionModel.elasticApiKey = apiKey;
elasticConnectionModel.elasticApiSecret = apiSecret;
- elasticConnectionModel.indexPrefix = indexPrefix +
System.currentTimeMillis();
+ elasticConnectionModel.indexPrefix = indexPrefix;
} catch (URISyntaxException e) {
LOG.error("Provided elastic connection string is not valid ", e);
}
@@ -118,7 +126,7 @@ public class ElasticConnectionRule extends ExternalResource
{
elasticConnectionModel.elasticPort =
elastic.getMappedPort(ElasticConnection.DEFAULT_PORT);
elasticConnectionModel.elasticApiKey = null;
elasticConnectionModel.elasticApiSecret = null;
- elasticConnectionModel.indexPrefix = indexPrefix +
System.currentTimeMillis();
+ elasticConnectionModel.indexPrefix = indexPrefix;
}
private Map<String, String> getUriQueryParams(URI uri) {
@@ -145,7 +153,7 @@ public class ElasticConnectionRule extends ExternalResource
{
String apiSecret = queryParams.get("key_secret");
return ElasticConnection.newBuilder()
- .withIndexPrefix(indexPrefix + System.currentTimeMillis())
+ .withIndexPrefix(indexPrefix)
.withConnectionParameters(scheme, host, port)
.withApiKeys(apiKey, apiSecret)
.build();
diff --git
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticFacetTest.java
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticFacetTest.java
index 6ede34abec..9a42b9c21a 100644
---
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticFacetTest.java
+++
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticFacetTest.java
@@ -28,7 +28,7 @@ public class ElasticFacetTest extends FacetCommonTest {
@ClassRule
public static final ElasticConnectionRule elasticRule =
- new
ElasticConnectionRule(ElasticTestUtils.ELASTIC_CONNECTION_STRING);
+ new
ElasticConnectionRule(ElasticTestUtils.ELASTIC_CONNECTION_STRING,
"elastic_test_");
protected Repository createJcrRepository() {
indexOptions = new ElasticIndexOptions();
diff --git
a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FacetCommonTest.java
b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FacetCommonTest.java
index 09e9bea7a5..92ef62c671 100644
---
a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FacetCommonTest.java
+++
b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FacetCommonTest.java
@@ -32,16 +32,17 @@ import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryResult;
+import javax.jcr.query.RowIterator;
import javax.jcr.security.Privilege;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
-import java.util.UUID;
import java.util.stream.Collectors;
import static org.apache.jackrabbit.commons.JcrUtils.getOrCreateByPath;
import static
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.FACETS;
+import static
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_RANDOM_SEED;
import static
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_REFRESH_DEFN;
import static
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_SECURE_FACETS;
import static
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_SECURE_FACETS_VALUE_INSECURE;
@@ -67,14 +68,19 @@ public abstract class FacetCommonTest extends
AbstractJcrTest {
private final Map<String, Integer> actualLabelCount = new HashMap<>();
private final Map<String, Integer> actualAclLabelCount = new HashMap<>();
private final Map<String, Integer> actualAclPar1LabelCount = new
HashMap<>();
+ private static final Random INDEX_SUFFIX_RANDOMIZER = new Random(7);
@Before
public void createIndex() throws RepositoryException {
- String indexName = UUID.randomUUID().toString();
IndexDefinitionBuilder builder =
indexOptions.createIndex(indexOptions.createIndexDefinitionBuilder(), false);
builder.noAsync().evaluatePathRestrictions();
builder.getBuilderTree().setProperty("jcr:primaryType",
"oak:QueryIndexDefinition", Type.NAME);
+ // Statistical facets in Elasticsearch use a random function with a
fixed seed but the results are not
+ // consistent when the index name changes. So we set the index name to
a fixed values.
+ String indexName = "FacetCommonTestIndex" +
INDEX_SUFFIX_RANDOMIZER.nextInt(1000);
+ builder.getBuilderTree().setProperty(PROP_RANDOM_SEED, 3000L,
Type.LONG);
+ builder.getBuilderTree().setProperty("indexNameSeed", 300L, Type.LONG);
IndexDefinitionBuilder.IndexRule indexRule =
builder.indexRule(JcrConstants.NT_BASE);
indexRule.property("cons").propertyIndex();
indexRule.property("foo").propertyIndex().getBuilderTree().setProperty(FACET_PROP,
true, Type.BOOLEAN);
@@ -173,18 +179,34 @@ public abstract class FacetCommonTest extends
AbstractJcrTest {
createDataset(NUM_LEAF_NODES_FOR_LARGE_DATASET);
- assertEventually(() -> assertEquals("Unexpected number of facets",
actualAclLabelCount.size(), getFacets().size()));
+ assertEventually(() -> {
+ Map<String, Integer> facets = getFacets();
+ assertEquals("Unexpected number of facets",
actualAclLabelCount.size(), facets.size());
- for (Map.Entry<String, Integer> facet :
actualAclLabelCount.entrySet()) {
- String facetLabel = facet.getKey();
- assertEventually(() -> {
- int facetCount = getFacets().get(facetLabel);
- float ratio = ((float) facetCount) / facet.getValue();
- assertTrue("Facet count for label: " + facetLabel + " is
outside of 10% margin of error. " +
- "Expected: " + facet.getValue() + "; Got: " +
facetCount + "; Ratio: " + ratio,
- Math.abs(ratio - 1) < 0.1);
- });
- }
+ for (Map.Entry<String, Integer> facet :
actualAclLabelCount.entrySet()) {
+ String facetLabel = facet.getKey();
+ assertEventually(() -> {
+ int facetCount = facets.get(facetLabel);
+ float ratio = ((float) facetCount) / facet.getValue();
+ assertTrue("Facet count for label: " + facetLabel + " is
outside of 10% margin of error. " +
+ "Expected: " + facet.getValue() + "; Got:
" + facetCount + "; Ratio: " + ratio,
+ Math.abs(ratio - 1) < 0.1);
+ });
+ }
+
+ try {
+ // Verify that the query result is not affected by the facet
sampling
+ int rowCounter = 0;
+ RowIterator rows = getQueryResult(null).getRows();
+ while (rows.hasNext()) {
+ rows.nextRow();
+ rowCounter++;
+ }
+ assertEquals("Unexpected number of rows", 3000, rowCounter);
+ } catch (RepositoryException e) {
+ throw new RuntimeException(e);
+ }
+ });
}
@Test
@@ -196,11 +218,14 @@ public abstract class FacetCommonTest extends
AbstractJcrTest {
createDataset(NUM_LEAF_NODES_FOR_SMALL_DATASET);
- assertEventually(() -> assertEquals("Unexpected number of facets",
actualAclLabelCount.size(), getFacets().size()));
+ assertEventually(() -> {
+ Map<String, Integer> facets = getFacets();
+ assertEquals("Unexpected number of facets",
actualAclLabelCount.size(), facets.size());
- // Since the hit count is less than sample size -> flow should have
switched to secure facet count instead of statistical
- // and thus the count should be exactly equal
- assertEventually(() -> assertEquals(actualAclLabelCount, getFacets()));
+ // Since the hit count is less than sample size -> flow should
have switched to secure facet count instead of statistical
+ // and thus the count should be exactly equal
+ assertEquals(actualAclLabelCount, facets);
+ });
}
@Test
@@ -242,19 +267,16 @@ public abstract class FacetCommonTest extends
AbstractJcrTest {
assertEventually(() -> {
Map<String, Integer> facets = getFacets();
assertEquals("Unexpected number of facets",
actualAclLabelCount.size(), facets.size());
- });
-
- for (Map.Entry<String, Integer> facet :
actualAclLabelCount.entrySet()) {
- assertEventually(() -> {
+ for (Map.Entry<String, Integer> facet :
actualAclLabelCount.entrySet()) {
String facetLabel = facet.getKey();
- int facetCount = getFacets().get(facetLabel);
+ int facetCount = facets.get(facetLabel);
float ratio = ((float) facetCount) / facet.getValue();
assertTrue("Facet count for label: " + facetLabel + " is
outside of 10% margin of error. " +
"Expected: " + facet.getValue() + "; Got: " +
facetCount + "; Ratio: " + ratio,
Math.abs(ratio - 1) < 0.1);
- });
- }
+ }
+ });
}
@Test
@@ -279,18 +301,16 @@ public abstract class FacetCommonTest extends
AbstractJcrTest {
assertEventually(() -> {
Map<String, Integer> facets = getFacets();
assertEquals("Unexpected number of facets",
actualLabelCount.size(), facets.size());
- });
- for (Map.Entry<String, Integer> facet : actualLabelCount.entrySet()) {
- assertEventually(() -> {
+ for (Map.Entry<String, Integer> facet :
actualLabelCount.entrySet()) {
String facetLabel = facet.getKey();
- int facetCount = getFacets().get(facetLabel);
+ int facetCount = facets.get(facetLabel);
float ratio = ((float) facetCount) / facet.getValue();
assertTrue("Facet count for label: " + facetLabel + " is
outside of 5% margin of error. " +
"Expected: " + facet.getValue() + "; Got: " +
facetCount + "; Ratio: " + ratio,
Math.abs(ratio - 1) < 0.05);
- });
- }
+ }
+ });
}
private Map<String, Integer> getFacets() {
@@ -308,11 +328,23 @@ public abstract class FacetCommonTest extends
AbstractJcrTest {
}
private Map<String, Integer> getFacets(String path) {
+ QueryResult queryResult = getQueryResult(path);
+ long start = LOG_PERF.start("Getting the Facet Results...");
+ FacetResult facetResult = new FacetResult(queryResult);
+ LOG_PERF.end(start, -1, "Facet Results fetched");
+
+ return facetResult.getDimensions()
+ .stream()
+ .flatMap(dim ->
Objects.requireNonNull(facetResult.getFacets(dim)).stream())
+ .collect(Collectors.toMap(FacetResult.Facet::getLabel,
FacetResult.Facet::getCount));
+ }
+
+ private QueryResult getQueryResult(String path) {
String pathCons = "";
if (path != null) {
pathCons = " AND ISDESCENDANTNODE('" + path + "')";
}
- String query = "SELECT [rep:facet(foo)], [rep:facet(bar)],
[rep:facet(baz)] FROM [nt:base] WHERE [cons] = 'val'" + pathCons;
+ String query = "SELECT [jcr:path], [rep:facet(foo)], [rep:facet(bar)],
[rep:facet(baz)] FROM [nt:base] WHERE [cons] = 'val'" + pathCons;
Query q;
QueryResult queryResult;
try {
@@ -321,14 +353,7 @@ public abstract class FacetCommonTest extends
AbstractJcrTest {
} catch (RepositoryException e) {
throw new RuntimeException(e);
}
- long start = LOG_PERF.start("Getting the Facet Results...");
- FacetResult facetResult = new FacetResult(queryResult);
- LOG_PERF.end(start, -1, "Facet Results fetched");
-
- return facetResult.getDimensions()
- .stream()
- .flatMap(dim ->
Objects.requireNonNull(facetResult.getFacets(dim)).stream())
- .collect(Collectors.toMap(FacetResult.Facet::getLabel,
FacetResult.Facet::getCount));
+ return queryResult;
}
protected void assertEventually(Runnable r) {