Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java (revision 1722863) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java (working copy) @@ -923,8 +923,12 @@ lock.unlock(); } } + invalidateCache(keys); } } catch (MongoException e) { + for (String k : keys) { + nodesCache.invalidate(k); + } throw DocumentStoreException.convert(e); } } finally { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java (revision 1722863) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java (working copy) @@ -280,6 +280,7 @@ public void update(Collection collection, List keys, UpdateOp updateOp) { UpdateUtils.assertUnconditional(updateOp); internalUpdate(collection, keys, updateOp); + invalidateCache(keys); } @Override @@ -313,7 +314,10 @@ @Override public CacheInvalidationStats invalidateCache(Iterable keys) { //TODO: optimize me - return invalidateCache(); + for (String k : keys) { + invalidateCache(Collection.NODES, k); + } + return null; } @Override Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java (revision 1722863) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java (working copy) @@ -414,6 +414,35 @@ } @Test + public void testModifyModified() { + // https://issues.apache.org/jira/browse/OAK-2940 + String id = this.getClass().getName() + ".testModifyModified"; + // create a test node + UpdateOp up = new UpdateOp(id, true); + up.set("_id", id); + up.set("_modified", 1000L); + boolean success = super.ds.create(Collection.NODES, Collections.singletonList(up)); + assertTrue(success); + removeMe.add(id); + + // update with "max" operation + up = new UpdateOp(id, false); + up.set("_id", id); + up.max("_modified", 2000L); + super.ds.update(Collection.NODES, Collections.singletonList(id), up); + NodeDocument nd = super.ds.find(Collection.NODES, id, 0); + assertEquals(((Number)nd.get("_modified")).longValue(), 2000L); + + // update with "set" operation + up = new UpdateOp(id, false); + up.set("_id", id); + up.set("_modified", 1500L); + super.ds.update(Collection.NODES, Collections.singletonList(id), up); + nd = super.ds.find(Collection.NODES, id, 0); + assertEquals(((Number)nd.get("_modified")).longValue(), 1500L); + } + + @Test public void testInterestingStrings() { // test case "gclef:\uD834\uDD1E" will fail on MySQL unless properly configured to use utf8mb4 charset // Assume.assumeTrue(!(super.dsname.equals("RDB-MySQL"))); Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MultiDocumentStoreTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MultiDocumentStoreTest.java (revision 1722863) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MultiDocumentStoreTest.java (working copy) @@ -66,82 +66,6 @@ } @Test - public void testInterleavedUpdate2() { - String id = this.getClass().getName() + ".testInterleavedUpdate2"; - - // remove if present - NodeDocument nd1 = super.ds1.find(Collection.NODES, id); - if (nd1 != null) { - super.ds1.remove(Collection.NODES, id); - } - - UpdateOp up = new UpdateOp(id, true); - up.set("_id", id); - up.set("_modified", 1L); - assertTrue(super.ds1.create(Collection.NODES, Collections.singletonList(up))); - removeMe.add(id); - - nd1 = super.ds1.find(Collection.NODES, id, 0); - Number n = nd1.getModCount(); - if (n != null) { - // Document store uses modCount - int n1 = n.intValue(); - - // get the document into ds2's cache - NodeDocument nd2 = super.ds2.find(Collection.NODES, id, 0); - int n2 = nd2.getModCount().intValue(); - assertEquals(n1, n2); - - UpdateOp upds1 = new UpdateOp(id, true); - upds1.set("_id", id); - upds1.set("foo", "bar"); - upds1.set("_modified", 2L); - super.ds1.update(Collection.NODES, Collections.singletonList(id), upds1); - nd1 = super.ds1.find(Collection.NODES, id); - int oldn1 = n1; - n1 = nd1.getModCount().intValue(); - assertEquals(oldn1 + 1, n1); - assertEquals("bar", nd1.get("foo")); - - // modify in DS2 - UpdateOp upds2 = new UpdateOp(id, true); - upds2.set("_id", id); - upds2.set("foo", "qux"); - upds2.set("_modified", 3L); - super.ds2.update(Collection.NODES, Collections.singletonList(id), upds2); - nd2 = super.ds2.find(Collection.NODES, id); - n2 = nd2.getModCount().intValue(); - assertEquals(oldn1 + 1, n2); - assertEquals("qux", nd2.get("foo")); - - // both stores are now at the same modCount with different contents - upds1 = new UpdateOp(id, true); - upds1.set("_id", id); - upds1.set("foo", "barbar"); - upds1.max("_modified", 0L); - NodeDocument prev = super.ds1.findAndUpdate(Collection.NODES, upds1); - // prev document should contain mod from DS2 - assertEquals("qux", prev.get("foo")); - assertEquals(oldn1 + 2, prev.getModCount().intValue()); - assertEquals(3L, prev.getModified().intValue()); - - // the new document must not have a _modified time smaller than - // before the update - nd1 = super.ds1.find(Collection.NODES, id, 0); - assertEquals(super.dsname + ": _modified value must never ever get smaller", 3L, nd1.getModified().intValue()); - - // verify that _modified can indeed be *set* to a smaller value, see - // https://issues.apache.org/jira/browse/OAK-2940 - upds1 = new UpdateOp(id, true); - upds1.set("_id", id); - upds1.set("_modified", 0L); - super.ds1.findAndUpdate(Collection.NODES, upds1); - nd1 = super.ds1.find(Collection.NODES, id, 0); - assertEquals(super.dsname + ": _modified value must be set to 0", 0L, nd1.getModified().intValue()); - } - } - - @Test public void testInvalidateCache() { // use a "proper" ID because otherwise Mongo's cache invalidation will fail // see OAK-2588 @@ -209,6 +133,43 @@ assertTrue(nd1.getLastCheckTime() > ds1checktime); } + @Test + public void testChangeVisibility() { + String id = this.getClass().getName() + ".testChangeVisibility"; + + super.ds1.remove(Collection.NODES, id); + + UpdateOp up = new UpdateOp(id, true); + up.set("_id", id); + up.set("_foo", 0l); + up.set("_bar", 0l); + assertTrue(super.ds1.create(Collection.NODES, Collections.singletonList(up))); + removeMe.add(id); + NodeDocument orig = super.ds1.find(Collection.NODES, id); + + // only run test if DS supports modcount + if (orig.getModCount() != null) { + long origMc = orig.getModCount().longValue(); + + UpdateOp up2 = new UpdateOp(id, false); + up2.set("_id", id); + up2.increment("_foo", 1L); + super.ds2.update(Collection.NODES, Collections.singletonList(id), up2); + NodeDocument ds2doc = super.ds2.find(Collection.NODES, id); + long ds2Mc = ds2doc.getModCount().longValue(); + assertTrue("_modCount needs to be > " + origMc + " but was " + ds2Mc, ds2Mc > origMc); + + UpdateOp up1 = new UpdateOp(id, false); + up1.set("_id", id); + up1.increment("_bar", 1L); + super.ds1.update(Collection.NODES, Collections.singletonList(id), up1); + + NodeDocument ds1doc = super.ds1.find(Collection.NODES, id); + long ds1Mc = ds1doc.getModCount().longValue(); + assertTrue("_modCount needs to be > " + ds2Mc + " but was " + ds1Mc, ds1Mc > ds2Mc); + } + } + private static long letTimeElapse() { long ts = System.currentTimeMillis(); while (System.currentTimeMillis() == ts) {