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 87035907d2 OAK-10617: oak-search-elastic fix deadlock with
includePathRestrictions=false and multiple filtered results (#1276)
87035907d2 is described below
commit 87035907d26d89495f4c47faf827ac8e9f91f34f
Author: Fabrizio Fortino <[email protected]>
AuthorDate: Tue Jan 23 14:13:05 2024 +0100
OAK-10617: oak-search-elastic fix deadlock with
includePathRestrictions=false and multiple filtered results (#1276)
* OAK-10617: oak-search-elastic fix deadlock with
includePathRestrictions=false and multiple filtered results
* OAK-10617: always apply path restrictions in ES
* OAK-10617: remove use of guava from modified classes
* OAK-10617: updated docs
* OAK-10617: simplify logic to check if all listeners have process at least
one element
* OAK-10617: async iterator queue is now bounded to the read limit
* OAK-10617: small improvement in logic to check if all listeners have
process at least one element
* OAK-10617: minor improvement
* OAK-10617: minor improvement: use a BitSet to keep track of search hit
listeners
* OAK-10617: annotate ElasticResponseListener with @ProviderType
---
oak-doc/src/site/markdown/query/elastic.md | 5 +-
oak-search-elastic/pom.xml | 5 +
.../index/elastic/ElasticIndexDefinition.java | 5 +
.../index/elastic/query/ElasticRequestHandler.java | 68 +++++++------
.../query/async/ElasticResponseListener.java | 8 +-
.../query/async/ElasticResultRowAsyncIterator.java | 30 ++++--
.../facets/ElasticSecureFacetAsyncProvider.java | 3 +-
...ElasticIndexDescendantSpellcheckCommonTest.java | 8 ++
.../index/IndexPathRestrictionCommonTest.java | 105 ++++++++++-----------
9 files changed, 135 insertions(+), 102 deletions(-)
diff --git a/oak-doc/src/site/markdown/query/elastic.md
b/oak-doc/src/site/markdown/query/elastic.md
index 867470e505..2f6669d320 100644
--- a/oak-doc/src/site/markdown/query/elastic.md
+++ b/oak-doc/src/site/markdown/query/elastic.md
@@ -33,9 +33,8 @@ however there are differences:
* Indexes are NOT automatically built when needed:
They can be built by setting the `reindex` property to `true` or by using
the `oak-run` tool.
We recommend to build them using the `oak-run` tool.
-* `evaluatePathRestrictions` is only checked at query time (to keep the
compatibility with Lucene). The parent paths are
- always indexed. Changing this flag won't require a reindex then. It's
strongly suggested to enable it. This control
- might be removed in the future.
+* `evaluatePathRestrictions` cannot be disabled. The parent paths are always
indexed. Queries with path restrictions are
+ evaluated at index level when possible, otherwise they are evaluated at
repository level.
* `codec` is ignored.
* `compatVersion` is ignored.
* `useIfExists` is ignored.
diff --git a/oak-search-elastic/pom.xml b/oak-search-elastic/pom.xml
index a0fd53acc4..277d451a97 100644
--- a/oak-search-elastic/pom.xml
+++ b/oak-search-elastic/pom.xml
@@ -101,6 +101,11 @@
<artifactId>org.apache.felix.scr.annotations</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.versioning</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.component.annotations</artifactId>
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 82d0374fdd..95b3290bfe 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
@@ -59,6 +59,9 @@ public class ElasticIndexDefinition extends IndexDefinition {
public static final String QUERY_FETCH_SIZES = "queryFetchSizes";
public static final Long[] QUERY_FETCH_SIZES_DEFAULT = new Long[]{10L,
100L, 1000L};
+ public static final String QUERY_TIMEOUT_MS = "queryTimeoutMs";
+ public static final long QUERY_TIMEOUT_MS_DEFAULT = 60000;
+
public static final String TRACK_TOTAL_HITS = "trackTotalHits";
public static final Integer TRACK_TOTAL_HITS_DEFAULT = 10000;
@@ -138,6 +141,7 @@ public class ElasticIndexDefinition extends IndexDefinition
{
public final int numberOfShards;
public final int numberOfReplicas;
public final int[] queryFetchSizes;
+ public final long queryTimeoutMs;
public final Integer trackTotalHits;
public final String dynamicMapping;
public final boolean failOnError;
@@ -162,6 +166,7 @@ public class ElasticIndexDefinition extends IndexDefinition
{
this.similarityTagsBoost = getOptionalValue(defn,
SIMILARITY_TAGS_BOOST, SIMILARITY_TAGS_BOOST_DEFAULT);
this.queryFetchSizes = Arrays.stream(getOptionalValues(defn,
QUERY_FETCH_SIZES, Type.LONGS, Long.class, QUERY_FETCH_SIZES_DEFAULT))
.mapToInt(Long::intValue).toArray();
+ this.queryTimeoutMs = getOptionalValue(defn, QUERY_TIMEOUT_MS,
QUERY_TIMEOUT_MS_DEFAULT);
this.trackTotalHits = getOptionalValue(defn, TRACK_TOTAL_HITS,
TRACK_TOTAL_HITS_DEFAULT);
this.dynamicMapping = getOptionalValue(defn, DYNAMIC_MAPPING,
DYNAMIC_MAPPING_DEFAULT);
this.failOnError = getOptionalValue(defn, FAIL_ON_ERROR,
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 f04d38c2d2..2935ec45ce 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
@@ -596,49 +596,47 @@ public class ElasticRequestHandler {
nodeTypeConstraints.ifPresent(queries::add);
}
- if (elasticIndexDefinition.evaluatePathRestrictions()) {
- String path = FulltextIndex.getPathRestriction(plan);
- switch (filter.getPathRestriction()) {
- case ALL_CHILDREN:
- if (!"/".equals(path)) {
- queries.add(newAncestorQuery(path));
+ String path = FulltextIndex.getPathRestriction(plan);
+ switch (filter.getPathRestriction()) {
+ case ALL_CHILDREN:
+ if (!"/".equals(path)) {
+ queries.add(newAncestorQuery(path));
+ }
+ break;
+ case DIRECT_CHILDREN:
+ queries.add(Query.of(q -> q.bool(b ->
b.must(newAncestorQuery(path)).must(newDepthQuery(path, planResult)))));
+ break;
+ case EXACT:
+ // For transformed paths, we can only add path restriction if
absolute path to property can be deduced
+ if (planResult.isPathTransformed()) {
+ String parentPathSegment =
planResult.getParentPathSegment();
+ if (!any.test(PathUtils.elements(parentPathSegment), "*"))
{
+ queries.add(newPathQuery(path + parentPathSegment));
}
- break;
- case DIRECT_CHILDREN:
- queries.add(Query.of(q -> q.bool(b ->
b.must(newAncestorQuery(path)).must(newDepthQuery(path, planResult)))));
- break;
- case EXACT:
+ } else {
+ queries.add(newPathQuery(path));
+ }
+ break;
+ case PARENT:
+ if (PathUtils.denotesRoot(path)) {
+ // there's no parent of the root node
+ // we add a path that can not possibly occur because there
+ // is no way to say "match no documents" in Lucene
+ queries.add(newPathQuery("///"));
+ } else {
// For transformed paths, we can only add path restriction
if absolute path to property can be deduced
if (planResult.isPathTransformed()) {
String parentPathSegment =
planResult.getParentPathSegment();
if (!any.test(PathUtils.elements(parentPathSegment),
"*")) {
- queries.add(newPathQuery(path +
parentPathSegment));
+
queries.add(newPathQuery(PathUtils.getParentPath(path) + parentPathSegment));
}
} else {
- queries.add(newPathQuery(path));
+
queries.add(newPathQuery(PathUtils.getParentPath(path)));
}
- break;
- case PARENT:
- if (PathUtils.denotesRoot(path)) {
- // there's no parent of the root node
- // we add a path that can not possibly occur because
there
- // is no way to say "match no documents" in Lucene
- queries.add(newPathQuery("///"));
- } else {
- // For transformed paths, we can only add path
restriction if absolute path to property can be deduced
- if (planResult.isPathTransformed()) {
- String parentPathSegment =
planResult.getParentPathSegment();
- if
(!any.test(PathUtils.elements(parentPathSegment), "*")) {
-
queries.add(newPathQuery(PathUtils.getParentPath(path) + parentPathSegment));
- }
- } else {
-
queries.add(newPathQuery(PathUtils.getParentPath(path)));
- }
- }
- break;
- case NO_RESTRICTION:
- break;
- }
+ }
+ break;
+ case NO_RESTRICTION:
+ break;
}
for (Filter.PropertyRestriction pr : filter.getPropertyRestrictions())
{
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResponseListener.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResponseListener.java
index b49875a729..f3a770f6fe 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResponseListener.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResponseListener.java
@@ -20,17 +20,18 @@ import
co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import org.osgi.annotation.versioning.ProviderType;
-import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* Generic listener of Elastic response
*/
+@ProviderType
public interface ElasticResponseListener {
- Set<String> DEFAULT_SOURCE_FIELDS = Collections.singleton(FieldNames.PATH);
+ Set<String> DEFAULT_SOURCE_FIELDS = Set.of(FieldNames.PATH);
/**
* Returns the source fields this listener is interested on
@@ -68,8 +69,9 @@ public interface ElasticResponseListener {
/**
* This method is called for each {@link Hit} retrieved
* @param searchHit a search result
+ * @return true if the search hit was successfully processed, false
otherwise
*/
- void on(Hit<ObjectNode> searchHit);
+ boolean on(Hit<ObjectNode> searchHit);
}
/**
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 b350762fe8..b0da1da4f7 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
@@ -41,6 +41,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
+import java.util.BitSet;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -48,6 +49,7 @@ import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
@@ -69,7 +71,7 @@ public class ElasticResultRowAsyncIterator implements
ElasticQueryIterator, Elas
private static final FulltextResultRow POISON_PILL =
new FulltextResultRow("___OAK_POISON_PILL___", 0d,
Collections.emptyMap(), null, null);
- private final BlockingQueue<FulltextResultRow> queue = new
LinkedBlockingQueue<>();
+ private final BlockingQueue<FulltextResultRow> queue;
private final ElasticIndexNode indexNode;
private final IndexPlan indexPlan;
@@ -96,6 +98,9 @@ public class ElasticResultRowAsyncIterator implements
ElasticQueryIterator, Elas
this.rowInclusionPredicate = rowInclusionPredicate;
this.metricHandler = metricHandler;
this.elasticFacetProvider =
elasticRequestHandler.getAsyncFacetProvider(indexNode.getConnection(),
elasticResponseHandler);
+ // set the queue size to the limit of the query. This is to avoid to
load too many results in memory in case the
+ // consumer is slow to process them
+ this.queue = new LinkedBlockingQueue<>((int)
indexPlan.getFilter().getQueryLimits().getLimitReads());
this.elasticQueryScanner = initScanner();
}
@@ -108,7 +113,7 @@ public class ElasticResultRowAsyncIterator implements
ElasticQueryIterator, Elas
elasticQueryScanner.scan();
}
try {
- nextRow = queue.take();
+ nextRow = queue.poll(indexNode.getDefinition().queryTimeoutMs,
TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore interrupt
status
throw new IllegalStateException("Error reading next result
from Elastic", e);
@@ -144,12 +149,12 @@ public class ElasticResultRowAsyncIterator implements
ElasticQueryIterator, Elas
}
@Override
- public void on(Hit<ObjectNode> searchHit) {
+ public boolean on(Hit<ObjectNode> searchHit) {
final String path = elasticResponseHandler.getPath(searchHit);
if (path != null) {
if (rowInclusionPredicate != null &&
!rowInclusionPredicate.test(path)) {
LOG.trace("Path {} not included because of hierarchy inclusion
rules", path);
- return;
+ return false;
}
LOG.trace("Path {} satisfies hierarchy inclusion rules", path);
try {
@@ -159,7 +164,9 @@ public class ElasticResultRowAsyncIterator implements
ElasticQueryIterator, Elas
Thread.currentThread().interrupt(); // restore interrupt
status
throw new IllegalStateException("Error producing results into
the iterator queue", e);
}
+ return true;
}
+ return false;
}
@Override
@@ -333,16 +340,25 @@ public class ElasticResultRowAsyncIterator implements
ElasticQueryIterator, Elas
}
LOG.trace("Emitting {} search hits, for a total of {} scanned
results", searchHits.size(), scannedRows);
+
+ BitSet listenersWithHits = new
BitSet(searchHitListeners.size());
+
for (Hit<ObjectNode> hit : searchHits) {
- for (SearchHitListener l : searchHitListeners) {
- l.on(hit);
+ for (int index = 0; index < searchHitListeners.size();
index++) {
+ SearchHitListener l = searchHitListeners.get(index);
+ if (l.on(hit)) {
+ listenersWithHits.set(index);
+ }
}
}
+ // if any listener has not processed any hit, it means we need
to load more data since there could be
+ // listeners waiting for some results before triggering a new
scan
+ boolean areAllListenersProcessed =
listenersWithHits.cardinality() == searchHitListeners.size();
if (!anyDataLeft.get()) {
LOG.trace("No data left: closing scanner, notifying
listeners");
close();
- } else if (fullScan) {
+ } else if (fullScan || !areAllListenersProcessed) {
scan();
}
} else {
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 a02a6afcb1..dec588c62d 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
@@ -71,7 +71,7 @@ class ElasticSecureFacetAsyncProvider implements
ElasticFacetProvider, ElasticRe
}
@Override
- public void on(Hit<ObjectNode> searchHit) {
+ public boolean on(Hit<ObjectNode> searchHit) {
final String path = elasticResponseHandler.getPath(searchHit);
if (path != null && isAccessible.test(path)) {
for (String field: facetFields) {
@@ -90,6 +90,7 @@ class ElasticSecureFacetAsyncProvider implements
ElasticFacetProvider, ElasticRe
}
}
}
+ return true;
}
@Override
diff --git
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDescendantSpellcheckCommonTest.java
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDescendantSpellcheckCommonTest.java
index a428a54e0c..eb2e0c60fc 100644
---
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDescendantSpellcheckCommonTest.java
+++
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDescendantSpellcheckCommonTest.java
@@ -20,6 +20,8 @@ import org.apache.jackrabbit.oak.Oak;
import org.apache.jackrabbit.oak.jcr.Jcr;
import
org.apache.jackrabbit.oak.plugins.index.IndexDescendantSpellcheckCommonTest;
import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
import javax.jcr.Repository;
@@ -38,4 +40,10 @@ public class ElasticIndexDescendantSpellcheckCommonTest
extends IndexDescendantS
return jcr.createRepository();
}
+ @Override
+ @Test
+ @Ignore("OAK-10617: path restrictions are always applied. This test is not
applicable for Elastic")
+ public void descendantSuggestionRequirePathRestrictionIndex() throws
Exception {
+ super.descendantSuggestionRequirePathRestrictionIndex();
+ }
}
diff --git
a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexPathRestrictionCommonTest.java
b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexPathRestrictionCommonTest.java
index f28c59bb78..be2e2203b3 100644
---
a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexPathRestrictionCommonTest.java
+++
b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexPathRestrictionCommonTest.java
@@ -16,8 +16,6 @@
*/
package org.apache.jackrabbit.oak.plugins.index;
-import org.apache.jackrabbit.guava.common.collect.ImmutableSet;
-import org.apache.jackrabbit.guava.common.collect.Sets;
import org.apache.jackrabbit.oak.InitialContentHelper;
import org.apache.jackrabbit.oak.commons.junit.LogCustomizer;
import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
@@ -43,13 +41,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
-import static org.apache.jackrabbit.guava.common.collect.ImmutableSet.of;
import static org.apache.jackrabbit.JcrConstants.NT_BASE;
import static org.junit.Assert.assertEquals;
@@ -86,10 +84,26 @@ public abstract class IndexPathRestrictionCommonTest {
commit();
}
+ @Test
+ public void restrictedPathOnLargeDataset() throws Exception {
+ setupTestData(evaluatePathRestrictionsInIndex);
+
+ // create a significant number of nodes under /a
+ NodeBuilder aRootBuilder = rootBuilder.child("a");
+ for (int i = 0; i < 1000; i++) {
+ aRootBuilder.child("a" + i).child("j:c").setProperty("foo", "bar");
+ }
+ commit();
+
+ FilterImpl f = createFilter(root, NT_BASE);
+ f.restrictProperty("j:c/foo", Operator.EQUAL,
PropertyValues.newString("bar"));
+ f.restrictPath("/test", Filter.PathRestriction.DIRECT_CHILDREN);
+ validateResult(f, Set.of("/test/a"), null);
+ }
+
@Test
public void pathTransformationWithNoPathRestriction() throws Exception {
setupTestData(evaluatePathRestrictionsInIndex);
- FilterImpl f;
// NOTE : The expected results here might seem "incorrect"
// for example : /test doesn't really satisfy the condition
j:c/@foo=bar but still is in the result set
@@ -98,7 +112,7 @@ public abstract class IndexPathRestrictionCommonTest {
// Refer
FullTextIndexCommonTest#pathTransformationsWithNoPathRestrictions for seeing
the e2e behaviour when executing an xpath query.
// //*[j:c/foo = 'bar'] -> foo:bar -> transform 1 level up
- f = createFilter(root, NT_BASE);
+ FilterImpl f = createFilter(root, NT_BASE);
f.restrictProperty("j:c/foo", Operator.EQUAL,
PropertyValues.newString("bar"));
Map<String, Boolean> expectedMap = new LinkedHashMap<>();
expectedMap.put("/test/a", true);
@@ -107,12 +121,12 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap.put("/tmp/a", true);
expectedMap.put("/tmp", true);
expectedMap.put("/tmp/c/d", true);
- validateResult(f, ImmutableSet.of("/test", "/test/a", "/test/c/d",
"/tmp", "/tmp/a", "/tmp/c/d"), expectedMap);
+ validateResult(f, Set.of("/test", "/test/a", "/test/c/d", "/tmp",
"/tmp/a", "/tmp/c/d"), expectedMap);
// //*[*/foo = 'bar'] -> foo:bar -> transform 1 level up
f = createFilter(root, NT_BASE);
f.restrictProperty("*/foo", Operator.EQUAL,
PropertyValues.newString("bar"));
- validateResult(f, ImmutableSet.of("/test", "/test/a", "/test/c/d",
"/tmp", "/tmp/a", "/tmp/c/d"), expectedMap);
+ validateResult(f, Set.of("/test", "/test/a", "/test/c/d", "/tmp",
"/tmp/a", "/tmp/c/d"), expectedMap);
// //*[d/*/foo = 'bar'] -> foo:bar -> transform 2 level up
f = createFilter(root, NT_BASE);
@@ -123,10 +137,9 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap2.put("/test/c", true);
expectedMap2.put("/tmp", true);
expectedMap2.put("/tmp/c", true);
- validateResult(f, of("/", "/test", "/test/c", "/tmp", "/tmp/c"),
expectedMap2);
+ validateResult(f, Set.of("/", "/test", "/test/c", "/tmp", "/tmp/c"),
expectedMap2);
}
-
@Test
public void entryCountWithNoPathRestriction() throws Exception {
IndexDefinitionBuilder idxBuilder =
@@ -144,10 +157,8 @@ public abstract class IndexPathRestrictionCommonTest {
}
commit();
- FilterImpl f;
-
// //*[foo = 'bar']
- f = createFilter(root, NT_BASE);
+ FilterImpl f = createFilter(root, NT_BASE);
f.restrictProperty("foo", Operator.EQUAL,
PropertyValues.newString("bar"));
// equality check: we assume
FulltextIndexPlanner#DEFAULT_PROPERTY_WEIGHT different values
int cost = count / FulltextIndexPlanner.DEFAULT_PROPERTY_WEIGHT;
@@ -183,11 +194,9 @@ public abstract class IndexPathRestrictionCommonTest {
testRootBuilder.child("d").child("e").child("j:c").setProperty("foo",
"bar");
commit();
- FilterImpl f;
-
// Validation 1
// /jcr:root/test//*[j:c/foo = 'bar'] -> foo:bar :ancestors:/test ->
transform 1 level up
- f = createFilter(root, NT_BASE);
+ FilterImpl f = createFilter(root, NT_BASE);
f.restrictProperty("j:c/foo", Operator.EQUAL,
PropertyValues.newString("bar"));
f.restrictPath("/test", Filter.PathRestriction.ALL_CHILDREN);
@@ -212,7 +221,7 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap1.put("/tmp/c/d", false);
expectedMap1.put("/test/d/e", true);
}
- validateResult(f, of("/test/a", "/test/c/d", "/test/d/e"),
expectedMap1);
+ validateResult(f, Set.of("/test/a", "/test/c/d", "/test/d/e"),
expectedMap1);
// Validation 2
// /jcr:root/test//*[*/foo = 'bar'] -> foo:bar :ancestors:/test ->
transform 1 level up
@@ -241,7 +250,7 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap2.put("/tmp/c/d", false);
expectedMap2.put("/test/d/e", true);
}
- validateResult(f, of("/test/a", "/test/c/d", "/test/d/e"),
expectedMap2);
+ validateResult(f, Set.of("/test/a", "/test/c/d", "/test/d/e"),
expectedMap2);
// Validation 3
// /jcr:root/test//*[d/*/foo = 'bar'] -> foo:bar :ancestors:/test ->
transform 2 level up
@@ -277,7 +286,7 @@ public abstract class IndexPathRestrictionCommonTest {
// This is handled later in the QueryEngine and the final result
// of query like /jcr:root/test//*[d/*/foo = 'bar'] will not contain
/test/d and only return /test/c.
// This test is demonstrated at QueryEngine level in
FullTextIndexCommonTest#pathTransformationsWithPathRestrictions
- validateResult(f, of("/test/c", "/test/d"), expectedMap3);
+ validateResult(f, Set.of("/test/c", "/test/d"), expectedMap3);
// Validation 4
// /jcr:root/test//*[foo = 'bar'] -> foo:bar :ancestors:/test -> no
transformation
@@ -304,16 +313,15 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap4.put("/tmp/c/d/j:c", true);
expectedMap4.put("/test/d/e/j:c", false);
}
- validateResult(f, of("/tmp/a/j:c", "/tmp/b", "/tmp/c/d/j:c"),
expectedMap4);
+ validateResult(f, Set.of("/tmp/a/j:c", "/tmp/b", "/tmp/c/d/j:c"),
expectedMap4);
}
@Test
public void pathTransformationWithDirectChildrenPathRestriction() throws
Exception {
setupTestData(evaluatePathRestrictionsInIndex);
- FilterImpl f;
// /jcr:root/test/*[j:c/foo = 'bar'] -> foo:bar :ancestors:/test
:depth:[3 TO 3] -> transform 1 level up
- f = createFilter(root, NT_BASE);
+ FilterImpl f = createFilter(root, NT_BASE);
f.restrictProperty("j:c/foo", Operator.EQUAL,
PropertyValues.newString("bar"));
f.restrictPath("/test", Filter.PathRestriction.DIRECT_CHILDREN);
Map<String, Boolean> expectedMap = new LinkedHashMap<>();
@@ -327,7 +335,7 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap.put("/tmp", false);
expectedMap.put("/tmp/c/d", false);
}
- validateResult(f, of("/test/a"), expectedMap);
+ validateResult(f, Set.of("/test/a"), expectedMap);
// /jcr:root/test/*[*/foo = 'bar'] -> foo:bar :ancestors:/test
:depth:[3 TO 3] -> transform 1 level up
f = createFilter(root, NT_BASE);
@@ -344,7 +352,7 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap2.put("/tmp", false);
expectedMap2.put("/tmp/c/d", false);
}
- validateResult(f, of("/test/a"), expectedMap2);
+ validateResult(f, Set.of("/test/a"), expectedMap2);
// /jcr:root/test/*[d/*/foo = 'bar'] -> foo:bar :ancestors:/test
:depth:[4 TO 4] -> transform 2 level up
f = createFilter(root, NT_BASE);
@@ -360,16 +368,15 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap3.put("/tmp", false);
expectedMap3.put("/tmp/c", false);
}
- validateResult(f, of("/test/c"), expectedMap3);
+ validateResult(f, Set.of("/test/c"), expectedMap3);
}
@Test
public void pathTransformationWithExactPathRestriction() throws Exception {
setupTestData(evaluatePathRestrictionsInIndex);
- FilterImpl f;
// /jcr:root/test/a[j:c/foo = 'bar'] -> foo:bar :path:/test/a/j:c ->
transform 1 level up
- f = createFilter(root, NT_BASE);
+ FilterImpl f = createFilter(root, NT_BASE);
f.restrictProperty("j:c/foo", Operator.EQUAL,
PropertyValues.newString("bar"));
f.restrictPath("/test/a", Filter.PathRestriction.EXACT);
Map<String, Boolean> expectedMap = new LinkedHashMap<>();
@@ -377,7 +384,7 @@ public abstract class IndexPathRestrictionCommonTest {
// (because we don't need ancestor query here, a simple term query on
path term(:path) works which is always indexed)
// so expectedMap here would be same for both
expectedMap.put("/test/a", true);
- validateResult(f, of("/test/a"), expectedMap);
+ validateResult(f, Set.of("/test/a"), expectedMap);
// /jcr:root/test/a[*/foo = 'bar'] -> foo:bar -> transform 1 level up
+ filter path restriction
f = createFilter(root, NT_BASE);
@@ -393,7 +400,7 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap2.put("/tmp/a", false);
expectedMap2.put("/tmp", false);
expectedMap2.put("/tmp/c/d", false);
- validateResult(f, of("/test/a"), expectedMap2);
+ validateResult(f, Set.of("/test/a"), expectedMap2);
// /jcr:root/test/c[d/*/foo = 'bar'] -> foo:bar -> transform 2 level
up + filter path restriction
f = createFilter(root, NT_BASE);
@@ -408,7 +415,7 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap3.put("/test/c", true);
expectedMap3.put("/tmp", false);
expectedMap3.put("/tmp/c", false);
- validateResult(f, of("/test/c"), expectedMap3);
+ validateResult(f, Set.of("/test/c"), expectedMap3);
// /jcr:root/test/a/j:c[foo = 'bar'] -> foo:bar :path:/test/a/j:c ->
No Transformation
@@ -417,24 +424,22 @@ public abstract class IndexPathRestrictionCommonTest {
f.restrictPath("/test/a/j:c", Filter.PathRestriction.EXACT);
Map<String, Boolean> expectedMap4 = new LinkedHashMap<>();
expectedMap4.put("/test/a/j:c", true);
- validateResult(f, of("/test/a/j:c"), expectedMap4);
+ validateResult(f, Set.of("/test/a/j:c"), expectedMap4);
}
@Test
public void pathTransformationWithParentFilter() throws Exception {
setupTestData(evaluatePathRestrictionsInIndex);
- FilterImpl f;
-
// /jcr:root/test/a/b/j:c/..[j:c/foo = 'bar'] -> foo:bar
:path:/test/a/b/j:c -> transform 1 level up
- f = createFilter(root, NT_BASE);
+ FilterImpl f = createFilter(root, NT_BASE);
f.restrictProperty("j:c/foo", Operator.EQUAL,
PropertyValues.newString("bar"));
f.restrictPath("/test/c/d/j:c", Filter.PathRestriction.PARENT);
Map<String, Boolean> expectedMap = new LinkedHashMap<>();
// In case of PARENT path restriction, index can still serve path
restriction even if evaluatePathRestrictions is false
// (because we don't need ancestor query here, a simple term query on
path term(:path) works which is always indexed)
expectedMap.put("/test/c/d", true);
- validateResult(f, of("/test/c/d"), expectedMap);
+ validateResult(f, Set.of("/test/c/d"), expectedMap);
// /jcr:root/test/a/b/j:c/..[*/foo = 'bar'] -> foo:bar -> transform 1
level up
f = createFilter(root, NT_BASE);
@@ -452,7 +457,7 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap2.put("/tmp/a", true);
expectedMap2.put("/tmp", true);
expectedMap2.put("/tmp/c/d", true);
- validateResult(f, ImmutableSet.of("/test", "/test/a", "/test/c/d",
"/tmp", "/tmp/a", "/tmp/c/d"), expectedMap2);
+ validateResult(f, Set.of("/test", "/test/a", "/test/c/d", "/tmp",
"/tmp/a", "/tmp/c/d"), expectedMap2);
// /jcr:root/test/c/d/..[d/*/foo = 'bar'] -> foo:bar -> transform 2
level up
f = createFilter(root, NT_BASE);
@@ -465,7 +470,7 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap3.put("/test/c", true);
expectedMap3.put("/tmp", true);
expectedMap3.put("/tmp/c", true);
- validateResult(f, of("/", "/test", "/test/c", "/tmp", "/tmp/c"),
expectedMap3);
+ validateResult(f, Set.of("/", "/test", "/test/c", "/tmp", "/tmp/c"),
expectedMap3);
// /jcr:root/test/c/d/..[foo = 'bar'] -> foo:bar :path:/test/c/d -> No
Transformation
@@ -476,7 +481,7 @@ public abstract class IndexPathRestrictionCommonTest {
expectedMap4.put("/tmp2/a", true);
- validateResult(f, of("/tmp2/a"), expectedMap4);
+ validateResult(f, Set.of("/tmp2/a"), expectedMap4);
}
private void setupTestData(boolean evaluatePathRestrictionsInIndex) throws
Exception {
@@ -509,7 +514,6 @@ public abstract class IndexPathRestrictionCommonTest {
commit();
}
-
private void commit() throws Exception {
// This part of code is used by both lucene and elastic tests.
// The index definitions in these tests don't have async property set
@@ -536,13 +540,6 @@ public abstract class IndexPathRestrictionCommonTest {
}, 4000 * 5);
}
- private long getEstimatedCount(Filter f) {
- List<QueryIndex.IndexPlan> plans = index.getPlans(f, null, root);
- assertEquals("Only one plan must show up", 1, plans.size());
- QueryIndex.IndexPlan plan = plans.get(0);
- return plan.getEstimatedEntryCount();
- }
-
private static FilterImpl createFilter(NodeState root, String
nodeTypeName) {
NodeTypeInfoProvider nodeTypes = new
NodeStateNodeTypeInfoProvider(root);
NodeTypeInfo type = nodeTypes.getNodeTypeInfo(nodeTypeName);
@@ -571,25 +568,27 @@ public abstract class IndexPathRestrictionCommonTest {
try {
customLogs.starting();
Cursor cursor = index.query(plan, root);
- Set<String> paths = Sets.newHashSet();
+
+ Set<String> paths = new HashSet<>();
while (cursor.hasNext()) {
paths.add(cursor.next().getPath());
}
- assertEquals("Expected number log entries for post path
filtering", expectedPathMapForPostPathFilterLogEntries.size(),
customLogs.getLogs().size());
+ if (expectedPathMapForPostPathFilterLogEntries != null) {
+ assertEquals("Expected number log entries for post path
filtering", expectedPathMapForPostPathFilterLogEntries.size(),
customLogs.getLogs().size());
- List<String> expectedLogEntries =
expectedPathMapForPostPathFilterLogEntries.keySet()
- .stream()
- .map(path ->
getExpectedLogEntryForPostPathFiltering(path,
expectedPathMapForPostPathFilterLogEntries.get(path)))
- .collect(Collectors.toList());
+ List<String> expectedLogEntries =
expectedPathMapForPostPathFilterLogEntries.keySet()
+ .stream()
+ .map(path ->
getExpectedLogEntryForPostPathFiltering(path,
expectedPathMapForPostPathFilterLogEntries.get(path)))
+ .collect(Collectors.toList());
- assertEquals("Expected log entries for post path filtering",
expectedLogEntries.toString(), customLogs.getLogs().toString());
+ assertEquals("Expected log entries for post path
filtering", expectedLogEntries.toString(), customLogs.getLogs().toString());
+ }
assertEquals(f.toString(), expected, paths);
} finally {
customLogs.finished();
}
-
}, 3000 * 3);
}