blob: 0601513bfef51c632fd6118af470605f14bd866e [file] [log] [blame]
/*
* 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;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Key;
import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Operation;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.stats.Clock;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.COMMIT_ROOT;
import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.setCommitRoot;
import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.setModified;
import static org.apache.jackrabbit.oak.plugins.document.TestUtils.merge;
import static org.apache.jackrabbit.oak.plugins.document.UpdateOp.Operation.Type.REMOVE_MAP_ENTRY;
import static org.apache.jackrabbit.oak.plugins.document.UpdateOp.Operation.Type.SET_MAP_ENTRY;
import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getIdFromPath;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class NodeDocumentSweeperTest {
@Rule
public DocumentMKBuilderProvider builderProvider = new DocumentMKBuilderProvider();
private Clock clock;
private DocumentNodeStore ns;
private DocumentMK mk;
private DocumentStore store;
private MissingLastRevSeeker seeker;
@Before
public void before() throws Exception {
clock = new Clock.Virtual();
clock.waitUntil(System.currentTimeMillis());
DocumentMK.Builder builder = builderProvider.newBuilder();
builder.clock(clock).setAsyncDelay(0);
Revision.setClock(clock);
mk = builder.open();
ns = builder.getNodeStore();
store = ns.getDocumentStore();
seeker = builder.createMissingLastRevSeeker();
}
@AfterClass
public static void resetClock() {
Revision.resetClockToDefault();
}
@Test
public void sweepUncommittedBeforeHead() throws Exception {
Revision uncommitted = ns.newRevision();
NodeBuilder b = ns.getRoot().builder();
b.child("test");
merge(ns, b);
ns.runBackgroundUpdateOperations();
UpdateOp op = new UpdateOp(getIdFromPath("/test"), false);
op.setMapEntry("foo", uncommitted, "value");
setCommitRoot(op, uncommitted, 0);
setModified(op, uncommitted);
assertNotNull(store.findAndUpdate(NODES, op));
List<UpdateOp> ops = Lists.newArrayList();
Revision nextSweepStart = sweep(ops);
assertEquals(ns.getHeadRevision().getRevision(ns.getClusterId()), nextSweepStart);
assertEquals(1, ops.size());
op = ops.get(0);
Map<Key, Operation> changes = op.getChanges();
assertEquals(2, changes.size());
Operation o = changes.get(new Key(COMMIT_ROOT, uncommitted));
assertNotNull(o);
assertEquals(REMOVE_MAP_ENTRY, o.type);
o = changes.get(new Key("foo", uncommitted));
assertNotNull(o);
assertEquals(REMOVE_MAP_ENTRY, o.type);
}
@Test
public void sweepUncommittedAfterHead() throws Exception {
NodeBuilder b = ns.getRoot().builder();
b.child("test");
merge(ns, b);
ns.runBackgroundUpdateOperations();
Revision uncommitted = ns.newRevision();
UpdateOp op = new UpdateOp(getIdFromPath("/test"), false);
op.setMapEntry("foo", uncommitted, "value");
setCommitRoot(op, uncommitted, 0);
setModified(op, uncommitted);
assertNotNull(store.findAndUpdate(NODES, op));
List<UpdateOp> ops = Lists.newArrayList();
Revision nextSweepStart = sweep(ops);
assertEquals(ns.getHeadRevision().getRevision(ns.getClusterId()), nextSweepStart);
// must not sweep change newer than head
assertEquals(0, ops.size());
}
@Test
public void sweepUnmergedBranchCommit() throws Exception {
NodeBuilder b = ns.getRoot().builder();
b.child("test");
merge(ns, b);
String branchRev = mk.branch(null);
mk.commit("/test", "^\"foo\":\"value\"", branchRev, null);
// force a new head revision newer than branch commit
b = ns.getRoot().builder();
b.child("bar");
merge(ns, b);
ns.runBackgroundUpdateOperations();
List<UpdateOp> ops = Lists.newArrayList();
Revision nextSweepStart = sweep(ops);
// must not touch branch
assertEquals(ns.getHeadRevision().getRevision(ns.getClusterId()), nextSweepStart);
assertEquals(0, ops.size());
}
@Test
public void sweepMergedBranch() throws Exception {
String branchRev = mk.branch(null);
branchRev = mk.commit("/", "+\"foo\":{}", branchRev, null);
branchRev = mk.commit("/", "+\"bar\":{}", branchRev, null);
branchRev = mk.commit("/", "+\"baz\":{}", branchRev, null);
mk.merge(branchRev, null);
ns.runBackgroundUpdateOperations();
List<UpdateOp> ops = Lists.newArrayList();
Revision nextSweepStart = sweep(ops);
assertEquals(ns.getHeadRevision().getRevision(ns.getClusterId()), nextSweepStart);
assertEquals(0, ops.size());
}
@Test
public void updatePre18Branch() throws Exception {
String branchRev = mk.branch(null);
branchRev = mk.commit("/", "+\"foo\":{}", branchRev, null);
mk.merge(branchRev, null);
ns.runBackgroundUpdateOperations();
// simulate a pre 1.8 branch commit by removing the branch commit entry
NodeDocument doc = store.find(NODES, getIdFromPath("/foo"));
assertNotNull(doc);
assertEquals(1, doc.getLocalBranchCommits().size());
UpdateOp op = new UpdateOp(doc.getId(), false);
for (Revision r : doc.getLocalBranchCommits()) {
NodeDocument.removeBranchCommit(op, r);
}
assertNotNull(store.findAndUpdate(NODES, op));
List<UpdateOp> ops = Lists.newArrayList();
Revision nextSweepStart = sweep(ops);
assertEquals(ns.getHeadRevision().getRevision(ns.getClusterId()), nextSweepStart);
assertEquals(1, ops.size());
op = ops.get(0);
Map<Key, Operation> changes = op.getChanges();
assertEquals(1, changes.size());
Key k = changes.keySet().iterator().next();
assertEquals("_bc", k.getName());
assertEquals(SET_MAP_ENTRY, changes.get(k).type);
}
private Revision sweep(final List<UpdateOp> ops) throws Exception {
NodeDocumentSweeper sweeper = new NodeDocumentSweeper(ns, false);
Revision startRev = ns.getSweepRevisions().getRevision(ns.getClusterId());
assertNotNull(startRev);
Iterable<NodeDocument> docs = seeker.getCandidates(startRev.getTimestamp());
return sweeper.sweep(docs, new NodeDocumentSweepListener() {
@Override
public void sweepUpdate(Map<Path, UpdateOp> updates)
throws DocumentStoreException {
ops.addAll(updates.values());
}
});
}
}