Merge remote-tracking branch 'origin/trunk' into OAK-9881
diff --git a/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/DataStoreBlobStoreStatsTest.java b/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/DataStoreBlobStoreStatsTest.java
index fb9379f..f3df903 100644
--- a/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/DataStoreBlobStoreStatsTest.java
+++ b/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/DataStoreBlobStoreStatsTest.java
@@ -964,7 +964,7 @@
}
private static <T, R> R waitForMetric(Function<T, R> f, T input, R expected, R defaultValue) {
- return waitForMetric(f, input, expected, defaultValue, 100, 1000);
+ return waitForMetric(f, input, expected, defaultValue, 100, 1500);
}
private static <T, R> R waitForMetric(Function<T, R> f, T input, R expected, R defaultValue, int intervalMilliseconds, int waitMilliseconds) {
@@ -988,7 +988,7 @@
}
private static <T> Long waitForNonzeroMetric(Function<T, Long> f, T input) {
- return waitForNonzeroMetric(f, input, 100, 1000);
+ return waitForNonzeroMetric(f, input, 100, 1500);
}
private static <T> Long waitForNonzeroMetric(Function<T, Long> f, T input, int intervalMilliseconds, int waitMilliseconds) {
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
index f096b64..c1914ae 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
@@ -29,6 +29,7 @@
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -227,7 +228,14 @@
w.println(line);
line = line.substring("commit".length()).trim();
apply(root, line);
- root.commit();
+ // This part of code is used by both lucene and elastic tests.
+ // The index definitions in these tests don't have async property set
+ // So lucene, in this case behaves in a sync manner. But the tests fail on Elastic,
+ // since ES indexing is always async.
+ // The below commit info map (sync-mode = rt) would make Elastic use RealTimeBulkProcessHandler.
+ // This would make ES indexes also sync.
+ // This will NOT have any impact on the lucene tests.
+ root.commit(Collections.singletonMap("sync-mode", "rt"));
}
w.flush();
}
diff --git a/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt b/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt
index 7168fe6..d7dfba9 100644
--- a/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt
+++ b/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt
@@ -407,21 +407,22 @@
commit /testRoot + "test3": { "name": "Hallo" }
commit /testRoot + "test4": { "name": "10%" }
commit /testRoot + "test5": { "name": "10 percent" }
+commit /testRoot + "test6": { "name": "brave" }
select a.name
from [nt:base] as a
where a.name is not null and isdescendantnode(a , '/testRoot') order by upper(a.name)
10 percent
10%
+brave
Hallo
hello
World!
select [jcr:path]
from [nt:base]
- where length(name) = 5
-/testRoot/test
-/testRoot/test3
+ where length(name) = 10
+/testRoot/test5
select [jcr:path]
from [nt:base]
@@ -440,8 +441,9 @@
select [jcr:path]
from [nt:base]
- where name like '%o_%'
-/testRoot/test2
+ where name like '%e_%'
+/testRoot/test
+/testRoot/test5
select [jcr:path]
from [nt:base]
diff --git a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryCommonTest.java b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryCommonTest.java
index 93f8da3..84d9419 100644
--- a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryCommonTest.java
+++ b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryCommonTest.java
@@ -23,18 +23,11 @@
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
import org.junit.After;
import org.junit.Rule;
-import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-
-import static org.junit.Assert.assertEquals;
-
import java.io.File;
-import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import javax.jcr.query.Query;
-
/**
* Tests the query engine using the default index implementation: the
* {@link LuceneIndexProvider}
@@ -59,14 +52,6 @@
executorService.shutdown();
}
- @Test
- public void descendantTestWithIndexTagExplain() throws Exception {
- List<String> result = executeQuery(
- "explain select [jcr:path] from [nt:base] where isdescendantnode('/test') option (index tag x)", Query.JCR_SQL2);
- assertEquals("[[nt:base] as [nt:base] /* lucene:test-index(/oak:index/test-index) :ancestors:/test\n"
- + " where isdescendantnode([nt:base], [/test]) */]", result.toString());
- }
-
@Override
public String getContainsValueForEqualityQuery_native() {
return "+:ancestors:/test +propa:bar";
@@ -91,4 +76,10 @@
public String getContainsValueForNotNullQuery_native() {
return "+:ancestors:/test +propa:[* TO *]";
}
+
+ @Override
+ public String getExplainValueForDescendantTestWithIndexTagExplain() {
+ return "[nt:base] as [nt:base] /* lucene:test-index(/oak:index/test-index) :ancestors:/test" +
+ " where isdescendantnode([nt:base], [/test]) */";
+ }
}
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 ce9fff3..3e51a86 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
@@ -214,6 +214,7 @@
return DEFAULT_SORTS;
}
Map<String, List<PropertyDefinition>> indexProperties = elasticIndexDefinition.getPropertiesByName();
+
boolean hasTieBreaker = false;
List<SortOptions> list = new ArrayList<>();
for (QueryIndex.OrderEntry o : sortOrder) {
@@ -225,7 +226,12 @@
hasTieBreaker = true;
} else if (JCR_SCORE.equals(sortPropertyName)) {
fieldName = "_score";
- } else if (indexProperties.containsKey(sortPropertyName)) {
+ } else if (indexProperties.containsKey(sortPropertyName) || elasticIndexDefinition.getDefinedRules()
+ .stream().anyMatch(rule -> rule.getConfig(sortPropertyName) != null)) {
+ // There are 2 conditions in this if statement -
+ // First one returns true if sortPropertyName is one of the defined indexed properties on the index
+ // Second condition returns true if sortPropertyName might not be explicitly defined but covered by a regex property
+ // in any of the defined index rules.
fieldName = elasticIndexDefinition.getElasticKeyword(sortPropertyName);
} else {
LOG.warn("Unable to sort by {} for index {}", sortPropertyName, elasticIndexDefinition.getIndexName());
@@ -732,11 +738,11 @@
return Query.of(q -> q.bool(bqBuilder.build()));
}
- private static Query nodeName(Filter.PropertyRestriction pr) {
+ private Query nodeName(Filter.PropertyRestriction pr) {
String first = pr.first != null ? pr.first.getValue(Type.STRING) : null;
if (pr.first != null && pr.first.equals(pr.last) && pr.firstIncluding && pr.lastIncluding) {
// [property]=[value]
- return Query.of(q -> q.term(t -> t.field(FieldNames.NODE_NAME).value(FieldValue.of(first))));
+ return Query.of(q -> q.term(t -> t.field(elasticIndexDefinition.getElasticKeyword(FieldNames.NODE_NAME)).value(FieldValue.of(first))));
}
if (pr.isLike) {
@@ -746,26 +752,32 @@
throw new IllegalStateException("For nodeName queries only EQUALS and LIKE are supported " + pr);
}
- private static Query like(String name, String first) {first = first.replace('%', WildcardQuery.WILDCARD_STRING);
+ private Query like(String name, String first) {
+ first = first.replace('%', WildcardQuery.WILDCARD_STRING);
first = first.replace('_', WildcardQuery.WILDCARD_CHAR);
// If the query ends in a wildcard string (*) and has no other wildcard characters, use a prefix match query
boolean hasSingleWildcardStringAtEnd = first.indexOf(WildcardQuery.WILDCARD_STRING) == first.length() - 1;
boolean doesNotContainWildcardChar = first.indexOf(WildcardQuery.WILDCARD_CHAR) == -1;
+ // Non full text (Non analyzed) properties are keyword types in ES. For those field would be equal to name.
+ // Analyzed properties, however are of text type on which we can't perform wildcard or prefix queries so we use the keyword (sub) field
+ // by appending .keyword to the name here.
+ String field = elasticIndexDefinition.getElasticKeyword(name);
+
if (hasSingleWildcardStringAtEnd && doesNotContainWildcardChar) {
// remove trailing "*" for prefix query
first = first.substring(0, first.length() - 1);
if (JCR_PATH.equals(name)) {
return newPrefixPathQuery(first);
} else {
- return newPrefixQuery(name, first);
+ return newPrefixQuery(field, first);
}
} else {
if (JCR_PATH.equals(name)) {
return newWildcardPathQuery(first);
} else {
- return newWildcardQuery(name, first);
+ return newWildcardQuery(field, first);
}
}
}
diff --git a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexQueryCommonTest.java b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexQueryCommonTest.java
index 4594893..5f05e98 100644
--- a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexQueryCommonTest.java
+++ b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexQueryCommonTest.java
@@ -21,14 +21,6 @@
import org.apache.jackrabbit.oak.plugins.index.IndexQueryCommonTest;
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
import org.junit.ClassRule;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.List;
-
-import javax.jcr.query.Query;
public class ElasticIndexQueryCommonTest extends IndexQueryCommonTest {
@@ -48,15 +40,6 @@
return repositoryOptionsUtil.getOak().createContentRepository();
}
- @Test
- public void descendantTestWithIndexTagExplain() {
- List<String> result = executeQuery(
- "explain select [jcr:path] from [nt:base] where isdescendantnode('/test') option (index tag x)", Query.JCR_SQL2);
- assertEquals("[[nt:base] as [nt:base] /* elasticsearch:test-index(/oak:index/test-index) "
- + "{\"bool\":{\"filter\":[{\"term\":{\":ancestors\":{\"value\":\"/test\"}}}]}}\n"
- + " where isdescendantnode([nt:base], [/test]) */]", result.toString());
- }
-
@Override
public String getContainsValueForEqualityQuery_native() {
return "\"filter\":[{\"term\":{\":ancestors\":{\"value\":\"/test\"}}},{\"term\":{\"propa.keyword\":{\"value\":\"bar\"}}}]";
@@ -86,17 +69,10 @@
}
@Override
- @Ignore("Failing on ES")
- @Test
- public void isChildNodeTest() throws Exception {
- super.isChildNodeTest();
- }
-
- @Override
- @Ignore("OAK-9858")
- @Test
- public void sql2FullText() throws Exception {
- super.sql2FullText();
+ public String getExplainValueForDescendantTestWithIndexTagExplain() {
+ return "[nt:base] as [nt:base] /* elasticsearch:test-index(/oak:index/test-index) "
+ + "{\"bool\":{\"filter\":[{\"term\":{\":ancestors\":{\"value\":\"/test\"}}}]}}"
+ + " where isdescendantnode([nt:base], [/test]) */";
}
}
diff --git a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FunctionIndexCommonTest.java b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FunctionIndexCommonTest.java
index e56b718..e4c6f94 100644
--- a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FunctionIndexCommonTest.java
+++ b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FunctionIndexCommonTest.java
@@ -222,6 +222,36 @@
}
@Test
+ public void path() throws Exception {
+ Tree index = createIndex("pathIndex", Collections.<String>emptySet());
+ Tree func = index.addChild(FulltextIndexConstants.INDEX_RULES)
+ .addChild("nt:base")
+ .addChild(FulltextIndexConstants.PROP_NODE)
+ .addChild("pathFunction");
+ func.setProperty(FulltextIndexConstants.PROP_FUNCTION, "path()");
+
+ Tree test = root.getTree("/").addChild("test");
+ test.addChild("hello");
+ test.addChild("world");
+ test.addChild("hello world");
+ root.commit();
+ postCommitHook();
+
+ String query = "select [jcr:path] from [nt:base] where path() = '/test/world'";
+ assertThat(explain(query), containsString(getIndexProvider() + "pathIndex(/oak:index/pathIndex)"));
+ assertQuery(query, asList("/test/world"));
+
+ query = "select [jcr:path] from [nt:base] where path() like '%hell%'";
+ assertThat(explain(query), containsString(getIndexProvider() + "pathIndex(/oak:index/pathIndex)"));
+ assertQuery(query, asList("/test/hello", "/test/hello world"));
+
+ query = "select [jcr:path] from [nt:base] where path() like '%ll_'";
+ assertThat(explain(query), containsString(getIndexProvider() + "pathIndex(/oak:index/pathIndex)"));
+ assertQuery(query, asList("/test/hello"));
+
+ }
+
+ @Test
public void testOrdering2() throws Exception {
Tree index = root.getTree("/");
Tree indexDefn = createTestIndexNode(index, indexOptions.getIndexType());
diff --git a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexQueryCommonTest.java b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexQueryCommonTest.java
index b8e0765..ed9c7ba 100644
--- a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexQueryCommonTest.java
+++ b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexQueryCommonTest.java
@@ -56,7 +56,6 @@
@Override
protected void createTestIndexNode() throws Exception {
- setTraversalEnabled(false);
Tree index = root.getTree("/");
indexDefn = createTestIndexNode(index, indexOptions.getIndexType());
TestUtil.useV2(indexDefn);
@@ -78,16 +77,27 @@
Tree dateProp = TestUtil.enableForOrdered(props, "propDate");
dateProp.setProperty(FulltextIndexConstants.PROP_TYPE, "Date");
+ // Note - certain tests in this class like #sql2 test regex based like queries.
+ // And since all the tests here use this common full text index - please be careful while adding any new properties.
+ // For example - #sql2() tests with a query on length of name property.
+ // Since this is a fulltext index with a regex property that indexes everything, those property names are also indexed.
+ // So if we add any property with propName that has length equal to what that test expects - that will effectively break the #sql2() test (giving more results).
+ // Ideally one would see the test failing while adding new properties - but there have been cases where this test was ignored due to a different reason
+ // and adding a new property added more failure reasons.
+
+ // So just be careful while changing the test collateral/setup here.
+
root.commit();
}
+ // TODO : The below 3 tests - #sql1, #sq2 and #sql2FullText need refactoring.
+ // These are huge tests with multiple queries running and verification happening in the end by comparing against results in an expected test file.
+ // These could possibly be broken down into several smaller tests instead which would make debugging much easier.
@Test
public void sql1() throws Exception {
test("sql1.txt");
}
- // TODO: Test failing on Lucene and ES
- @Ignore("OAK-9858")
@Test
public void sql2() throws Exception {
test("sql2.txt");
@@ -168,6 +178,26 @@
}
@Test
+ public void descendantTestWithIndexTagExplain() throws Exception {
+ Tree test = root.getTree("/").addChild("test");
+ test.addChild("a");
+ test.addChild("b");
+ root.commit();
+
+ String query = "explain select [jcr:path] from [nt:base] where isdescendantnode('/test') option (index tag x)";
+ assertEventually(getAssertionForExplain(query, Query.JCR_SQL2, getExplainValueForDescendantTestWithIndexTagExplain(), true));
+ }
+
+ // Check if this is a valid behaviour or not ?
+ // This was discovered when we removed setTraversalEnabled(false); from the test setup.
+ @Ignore("Index not picked even when using option tag if traversal cost is lower")
+ @Test
+ public void descendantTestWithIndexTagExplainWithNoData() {
+ String query = "explain select [jcr:path] from [nt:base] where isdescendantnode('/test') option (index tag x)";
+ assertEventually(getAssertionForExplain(query, Query.JCR_SQL2, getExplainValueForDescendantTestWithIndexTagExplain(), true));
+ }
+
+ @Test
public void descendantTest2() throws Exception {
Tree test = root.getTree("/").addChild("test");
test.addChild("a").setProperty("name", asList("Hello", "World"), STRINGS);
@@ -184,8 +214,6 @@
});
}
- // TODO: Test failing on Lucene and ES
- @Ignore("OAK-9859")
@Test
public void isChildNodeTest() throws Exception {
Tree tree = root.getTree("/");
@@ -573,7 +601,7 @@
String query = "explain /jcr:root/test//*[propa!='bar']";
- assertEventually(getAssertionForExplainContains(query, XPATH, getContainsValueForInequalityQuery_native()));
+ assertEventually(getAssertionForExplain(query, XPATH, getContainsValueForInequalityQuery_native(), false));
String query2 = "/jcr:root/test//*[propa!='bar']";
@@ -592,7 +620,7 @@
String query = "explain select * from [nt:base] as s where propa is not null and ISDESCENDANTNODE(s, '/test')";
- assertEventually(getAssertionForExplainContains(query, SQL2, getContainsValueForNotNullQuery_native()));
+ assertEventually(getAssertionForExplain(query, SQL2, getContainsValueForNotNullQuery_native(), false));
String query2 = "select * from [nt:base] as s where propa is not null and ISDESCENDANTNODE(s, '/test')";
@@ -612,7 +640,7 @@
String query = "explain //*[propa!='bar']";
- assertEventually(getAssertionForExplainContains(query, XPATH, getContainsValueForInequalityQueryWithoutAncestorFilter_native()));
+ assertEventually(getAssertionForExplain(query, XPATH, getContainsValueForInequalityQueryWithoutAncestorFilter_native(), false));
String query2 = "//*[propa!='bar']";
@@ -632,7 +660,7 @@
root.commit();
String query = "explain /jcr:root/test//*[propa!='bar' and propb='world']";
- assertEventually(getAssertionForExplainContains(query, XPATH, getContainsValueForEqualityInequalityCombined_native()));
+ assertEventually(getAssertionForExplain(query, XPATH, getContainsValueForEqualityInequalityCombined_native(), false));
String query2 = "/jcr:root/test//*[propa!='bar' and propb='world']";
// Expected - nodes with both properties defined and propb with value 'world' and propa with value not equal to bar should be returned
@@ -653,7 +681,7 @@
String query = "explain /jcr:root/test//*[propa='bar']";
- assertEventually(getAssertionForExplainContains(query, XPATH, getContainsValueForEqualityQuery_native()));
+ assertEventually(getAssertionForExplain(query, XPATH, getContainsValueForEqualityQuery_native(), false));
String query2 = "/jcr:root/test//*[propa='bar']";
@@ -752,7 +780,9 @@
public abstract String getContainsValueForNotNullQuery_native();
- private Runnable getAssertionForExplainContains(String query, String language, String containValue) {
+ public abstract String getExplainValueForDescendantTestWithIndexTagExplain();
+
+ private Runnable getAssertionForExplain(String query, String language, String expected, boolean matchComplete) {
return () -> {
Result result = null;
try {
@@ -761,7 +791,11 @@
fail(e.getMessage());
}
ResultRow row = result.getRows().iterator().next();
- assertTrue(row.getValue("plan").toString().contains(containValue));
+ if (matchComplete) {
+ assertEquals(row.getValue("plan").toString(), expected);
+ } else {
+ assertTrue(row.getValue("plan").toString().contains(expected));
+ }
};
}
diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/cache/NodeDocumentCache.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/cache/NodeDocumentCache.java
index 95f147d..dc28e01 100644
--- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/cache/NodeDocumentCache.java
+++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/cache/NodeDocumentCache.java
@@ -438,13 +438,23 @@
Lock lock = locks.acquire(id);
try {
NodeDocument cachedDoc = getIfPresent(id);
- // if an old document is present in the cache, we can simply update it
if (cachedDoc != null && isNewer(cachedDoc, d)) {
+ // if an old document is present in the cache, we can simply update it
putInternal(d, tracker);
- // if the document hasn't been invalidated or added during the tracker lifetime,
- // we can put it as well
} else if (cachedDoc == null && !tracker.mightBeenAffected(id)) {
+ // if the document hasn't been invalidated or added during the tracker lifetime,
+ // we can put it as well
putInternal(d, tracker);
+ } else {
+ // in all other cases we don't know if the current document
+ // is up-to-date. notify the other trackers and invalidate
+ // the cache
+ internalMarkChanged(id, tracker);
+ if (isLeafPreviousDocId(id)) {
+ prevDocumentsCache.invalidate(new StringValue(id));
+ } else {
+ nodeDocumentsCache.invalidate(new StringValue(id));
+ }
}
} finally {
lock.unlock();
diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
index f8c1095..8e69896 100644
--- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
+++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
@@ -1411,7 +1411,9 @@
List<String> resultKeys = new ArrayList<>(keys.size());
CacheChangesTracker tracker = null;
if (collection == Collection.NODES) {
- tracker = nodesCache.registerTracker(keys);
+ // keys set is modified later. create a copy of the keys set
+ // owned by the cache changes tracker.
+ tracker = nodesCache.registerTracker(new HashSet<>(keys));
}
Throwable t;
try {
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentPrefetchAndUpdateIT.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentPrefetchAndUpdateIT.java
index b6cc9c3..a707a77 100755
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentPrefetchAndUpdateIT.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentPrefetchAndUpdateIT.java
@@ -33,7 +33,6 @@
import org.jetbrains.annotations.Nullable;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
@@ -71,7 +70,6 @@
System.clearProperty(DocumentNodeStore.SYS_PROP_PREFETCH);
}
- @Ignore("OAK-9850")
@Test
public void cacheConsistency() throws Exception {
Revision r = newRevision();
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/cache/NodeDocumentCacheTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/cache/NodeDocumentCacheTest.java
new file mode 100644
index 0000000..6cb22db
--- /dev/null
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/cache/NodeDocumentCacheTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.document.cache;
+
+import org.apache.jackrabbit.oak.plugins.document.Document;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.NodeDocument;
+import org.apache.jackrabbit.oak.plugins.document.locks.NodeDocumentLocks;
+import org.apache.jackrabbit.oak.plugins.document.locks.StripedNodeDocumentLocks;
+import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
+import org.junit.Before;
+import org.junit.Test;
+
+import static java.util.Collections.singleton;
+import static org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilder.newDocumentNodeStoreBuilder;
+import static org.junit.Assert.assertEquals;
+
+public class NodeDocumentCacheTest {
+
+ private static final String ID = "some-id";
+
+ private final DocumentStore store = new MemoryDocumentStore();
+
+ private final NodeDocumentLocks locks = new StripedNodeDocumentLocks();
+
+ private NodeDocumentCache cache;
+
+ @Before
+ public void setup() {
+ cache = newDocumentNodeStoreBuilder()
+ .buildNodeDocumentCache(store, locks);
+ }
+
+ @Test
+ public void cacheConsistency() throws Exception {
+ NodeDocument current = createDocument(0L);
+ NodeDocument updated = createDocument(1L);
+
+ // an update operation starts and registers a tracker
+ CacheChangesTracker updateTracker = cache.registerTracker(singleton(ID));
+ // the document is invalidated. this informs the update tracker
+ cache.invalidate(ID);
+ // a query operation starts and registers a tracker
+ CacheChangesTracker queryTracker = cache.registerTracker(singleton(ID));
+ // the query operation is able to read the document before it is updated
+ // but then gets delayed.
+ // the update operation wants to put the document into the cache, but
+ // can't because its tracker was informed by the cache invalidation
+ cache.putNonConflictingDocs(updateTracker, singleton(updated));
+ // the query operation wants to put the outdated document into the
+ // cache. the cache must not accept this outdated document.
+ cache.putNonConflictingDocs(queryTracker, singleton(current));
+
+ assertEquals(updated.getModCount(), cache.get(ID, () -> updated).getModCount());
+ }
+
+ private NodeDocument createDocument(long modCount) {
+ NodeDocument doc = new NodeDocument(store, modCount);
+ doc.put(Document.ID, ID);
+ doc.put(Document.MOD_COUNT, modCount);
+ return doc;
+ }
+}
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/prefetch/CacheWarmingTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/prefetch/CacheWarmingTest.java
index 99cc90f..d391c21 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/prefetch/CacheWarmingTest.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/prefetch/CacheWarmingTest.java
@@ -16,7 +16,10 @@
*/
package org.apache.jackrabbit.oak.plugins.document.prefetch;
+import static org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore.SYS_PROP_PREFETCH;
import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getIdFromPath;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -30,6 +33,7 @@
import java.util.TreeSet;
import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.commons.junit.TemporarySystemProperty;
import org.apache.jackrabbit.oak.plugins.document.Collection;
import org.apache.jackrabbit.oak.plugins.document.CountingDocumentStore;
import org.apache.jackrabbit.oak.plugins.document.DocumentMK;
@@ -48,6 +52,7 @@
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.stats.Clock;
import org.jetbrains.annotations.Nullable;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -56,6 +61,9 @@
public class CacheWarmingTest {
@Rule
+ public TemporarySystemProperty systemProperties = new TemporarySystemProperty();
+
+ @Rule
public DocumentMKBuilderProvider builderProvider = new DocumentMKBuilderProvider();
@Rule
@@ -65,6 +73,11 @@
private CountingMongoDatabase db;
+ @Before
+ public void enablePrefetch() {
+ System.setProperty(SYS_PROP_PREFETCH, "true");
+ }
+
@Test
public void noop1() {
DocumentStore store = new MemoryDocumentStore();
@@ -164,7 +177,7 @@
SortedSet<String> children = new TreeSet<String>();
// create a bunch of nodes
// make it 4 levels deep to avoid things like 'readChildren' to be able to use optimizations such as query()
- for (int i = 0; i < 5*1024; i++) {
+ for (int i = 0; i < 4*1024; i++) {
String name = "c" + i;
children.add("/" + name + "/" + name + "/" + name + "/" + name);
builder.child(name).child(name).child(name).child(name);
@@ -180,19 +193,25 @@
store.getDocumentStore().invalidateCache();
logAndReset("after invalidate", cds, sw);
}
+ DocumentNodeState root = store.getRoot();
if (prefetch) {
final List<String> paths = new ArrayList<>(children);
final java.util.Collection<String> withParents = withParents(paths);
withParents.remove("/");
- store.prefetch(withParents, null);
+ store.prefetch(withParents, root);
logAndReset("after prefetch ", cds, sw);
}
// read the children again
- DocumentNodeState root = store.getRoot();
for (String aChild : root.getChildNodeNames()) {
assertTrue(root.getChildNode(aChild).getChildNode(aChild).getChildNode(aChild).getChildNode(aChild).exists());
}
+ // raw find calls must be reasonably low with prefetch
+ int rawFindCalls = getRawFindCalls();
logAndReset("read ", cds, sw);
+ if (prefetch) {
+ // OAK-9883 - this assertion is not stable, disable for now
+ // assertThat(rawFindCalls, lessThan(10));
+ }
}
public static java.util.Collection<String> withParents(java.util.Collection<String> paths) {