Author: mreutegg
Date: Wed Mar 26 20:34:36 2014
New Revision: 1582045
URL: http://svn.apache.org/r1582045
Log:
OAK-1342: Cascading document history
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PropertyHistory.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Range.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ValueMap.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentSplitTest.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RangeTest.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ValueMapTest.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java?rev=1582045&r1=1582044&r2=1582045&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
Wed Mar 26 20:34:36 2014
@@ -1298,7 +1298,7 @@ public final class DocumentNodeStore
if (isDisposed.get()) {
return;
}
- LOG.warn("Background operation failed: " + e.toString(), e);
+ throw e;
}
}
@@ -1622,7 +1622,11 @@ public final class DocumentNodeStore
}
DocumentNodeStore nodeStore = ref.get();
if (nodeStore != null) {
- nodeStore.runBackgroundOperations();
+ try {
+ nodeStore.runBackgroundOperations();
+ } catch (Throwable t) {
+ LOG.warn("Background operation failed: " +
t.toString(), t);
+ }
delay = nodeStore.getAsyncDelay();
} else {
// node store not in use anymore
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java?rev=1582045&r1=1582044&r2=1582045&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
Wed Mar 26 20:34:36 2014
@@ -44,6 +44,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -92,6 +93,12 @@ public final class NodeDocument extends
static final float SPLIT_RATIO = 0.3f;
/**
+ * Create an intermediate previous document when there are this many
+ * previous documents of equal height.
+ */
+ static final int PREV_SPLIT_FACTOR = 10;
+
+ /**
* Revision collision markers set by commits with modifications, which
* overlap with un-merged branch commits.
* Key: revision, value: always true
@@ -169,8 +176,9 @@ public final class NodeDocument extends
/**
* Properties to ignore when a document is split.
*/
- private static final Set<String> IGNORE_ON_SPLIT = ImmutableSet.of(ID,
MOD_COUNT, MODIFIED, PREVIOUS,
- LAST_REV, CHILDREN_FLAG, HAS_BINARY_FLAG);
+ private static final Set<String> IGNORE_ON_SPLIT = ImmutableSet.of(
+ ID, MOD_COUNT, MODIFIED, PREVIOUS, LAST_REV, CHILDREN_FLAG,
+ HAS_BINARY_FLAG, PATH);
public static final long HAS_BINARY_VAL = 1;
@@ -195,7 +203,7 @@ public final class NodeDocument extends
/**
* Required for serialization
*
- * @param store
+ * @param store the document store.
* @param creationTime time at which it was created. Would be different
from current time
* in case of being resurrected from a serialized for
*/
@@ -215,8 +223,7 @@ public final class NodeDocument extends
*/
@Nonnull
public Map<Revision, String> getValueMap(@Nonnull String key) {
- Object value = super.get(key);
- if (IGNORE_ON_SPLIT.contains(key) || !(value instanceof Map)) {
+ if (IGNORE_ON_SPLIT.contains(key)) {
return Collections.emptyMap();
} else {
return ValueMap.create(this, key);
@@ -236,7 +243,7 @@ public final class NodeDocument extends
*/
public boolean hasChildren() {
Boolean childrenFlag = (Boolean) get(CHILDREN_FLAG);
- return childrenFlag != null ? childrenFlag.booleanValue() : false;
+ return childrenFlag != null && childrenFlag;
}
/**
@@ -278,6 +285,27 @@ public final class NodeDocument extends
}
/**
+ * Returns the path of the main document if this document is part of a
_prev
+ * history tree. Otherwise this method simply returns {@link #getPath()}.
+ *
+ * @return the path of the main document.
+ */
+ @Nonnull
+ public String getMainPath() {
+ String p = getPath();
+ if (p.startsWith("p")) {
+ p = PathUtils.getAncestorPath(p, 2);
+ if (p.length() == 1) {
+ return "/";
+ } else {
+ return p.substring(1);
+ }
+ } else {
+ return p;
+ }
+ }
+
+ /**
* @return a map of the last known revision for each clusterId.
*/
@Nonnull
@@ -658,17 +686,24 @@ public final class NodeDocument extends
*/
@Nonnull
public Iterable<UpdateOp> split(@Nonnull RevisionContext context) {
+ SortedMap<Revision, Range> previous = getPreviousRanges();
// only consider if there are enough commits,
// unless document is really big
if (getLocalRevisions().size() + getLocalCommitRoot().size() <=
NUM_REVS_THRESHOLD
- && getMemory() < DOC_SIZE_THRESHOLD) {
+ && getMemory() < DOC_SIZE_THRESHOLD
+ && previous.size() < PREV_SPLIT_FACTOR) {
return Collections.emptyList();
}
+ String path = getPath();
String id = getId();
- SortedMap<Revision, Range> previous = getPreviousRanges();
+ if (id == null) {
+ throw new IllegalStateException("document does not have an id: " +
this);
+ }
// what's the most recent previous revision?
Revision recentPrevious = null;
- for (Revision rev : previous.keySet()) {
+ Map<Integer, List<Range>> prevHisto = Maps.newHashMap();
+ for (Map.Entry<Revision, Range> entry : previous.entrySet()) {
+ Revision rev = entry.getKey();
if (rev.getClusterId() != context.getClusterId()) {
continue;
}
@@ -676,6 +711,13 @@ public final class NodeDocument extends
|| isRevisionNewer(context, rev, recentPrevious)) {
recentPrevious = rev;
}
+ Range r = entry.getValue();
+ List<Range> list = prevHisto.get(r.getHeight());
+ if (list == null) {
+ list = new ArrayList<Range>();
+ prevHisto.put(r.getHeight(), list);
+ }
+ list.add(r);
}
Map<String, NavigableMap<Revision, String>> splitValues
= new HashMap<String, NavigableMap<Revision, String>>();
@@ -703,7 +745,7 @@ public final class NodeDocument extends
}
}
- List<UpdateOp> splitOps = Collections.emptyList();
+ List<UpdateOp> splitOps = Lists.newArrayList();
int numValues = 0;
Revision high = null;
Revision low = null;
@@ -724,19 +766,20 @@ public final class NodeDocument extends
}
numValues += splitMap.size();
}
+ UpdateOp main = null;
if (high != null && low != null
&& (numValues >= NUM_REVS_THRESHOLD
|| getMemory() > DOC_SIZE_THRESHOLD)) {
// enough revisions to split off
- splitOps = new ArrayList<UpdateOp>(2);
// move to another document
- UpdateOp main = new UpdateOp(id, false);
- setPrevious(main, high, low);
- UpdateOp old = new UpdateOp(Utils.getPreviousIdFor(id, high),
true);
- if (get(PATH) != null) {
- old.set(PATH, get(PATH));
- }
+ main = new UpdateOp(id, false);
+ setPrevious(main, new Range(high, low, 0));
+ String oldPath = Utils.getPreviousPathFor(path, high, 0);
+ UpdateOp old = new UpdateOp(Utils.getIdFromPath(oldPath), true);
old.set(ID, old.getId());
+ if (Utils.isLongPath(oldPath)) {
+ old.set(PATH, oldPath);
+ }
for (String property : splitValues.keySet()) {
NavigableMap<Revision, String> splitMap =
splitValues.get(property);
for (Map.Entry<Revision, String> entry : splitMap.entrySet()) {
@@ -751,9 +794,50 @@ public final class NodeDocument extends
// only split if half of the data can be moved to old document
if (oldDoc.getMemory() > getMemory() * SPLIT_RATIO) {
splitOps.add(old);
- splitOps.add(main);
}
}
+
+ // check if we need to create intermediate previous documents
+ for (Map.Entry<Integer, List<Range>> entry : prevHisto.entrySet()) {
+ if (entry.getValue().size() >= PREV_SPLIT_FACTOR) {
+ if (main == null) {
+ main = new UpdateOp(id, false);
+ }
+ // calculate range new range
+ Revision h = null;
+ Revision l = null;
+ for (Range r : entry.getValue()) {
+ if (h == null || isRevisionNewer(context, r.high, h)) {
+ h = r.high;
+ }
+ if (l == null || isRevisionNewer(context, l, r.low)) {
+ l = r.low;
+ }
+ removePrevious(main, r);
+ }
+ if (h == null || l == null) {
+ throw new IllegalStateException();
+ }
+ String prevPath = Utils.getPreviousPathFor(path, h,
entry.getKey() + 1);
+ String prevId = Utils.getIdFromPath(prevPath);
+ UpdateOp intermediate = new UpdateOp(prevId, true);
+ intermediate.set(ID, prevId);
+ if (Utils.isLongPath(prevPath)) {
+ intermediate.set(PATH, prevPath);
+ }
+ setPrevious(main, new Range(h, l, entry.getKey() + 1));
+ for (Range r : entry.getValue()) {
+ setPrevious(intermediate, r);
+ }
+ splitOps.add(intermediate);
+ }
+ }
+
+ // main document must be updated last
+ if (main != null && !splitOps.isEmpty()) {
+ splitOps.add(main);
+ }
+
return splitOps;
}
@@ -773,9 +857,8 @@ public final class NodeDocument extends
NavigableMap<Revision, Range> transformed = new
TreeMap<Revision, Range>(
StableRevisionComparator.REVERSE);
for (Map.Entry<Revision, String> entry : map.entrySet()) {
- Revision high = entry.getKey();
- Revision low = Revision.fromString(entry.getValue());
- transformed.put(high, new Range(high, low));
+ Range r = Range.fromEntry(entry.getKey(),
entry.getValue());
+ transformed.put(r.high, r);
}
previous = Maps.unmodifiableNavigableMap(transformed);
}
@@ -803,10 +886,13 @@ public final class NodeDocument extends
if (revision == null) {
return new PropertyHistory(store, this, property);
} else {
+ final String mainPath = getMainPath();
// first try to lookup revision directly
- Revision r = getPreviousRanges().floorKey(revision);
- if (r != null) {
- String prevId = Utils.getPreviousIdFor(getId(), r);
+ Map.Entry<Revision, Range> entry =
getPreviousRanges().floorEntry(revision);
+ if (entry != null) {
+ Revision r = entry.getKey();
+ int h = entry.getValue().height;
+ String prevId = Utils.getPreviousIdFor(mainPath, r, h);
NodeDocument prev = store.find(Collection.NODES, prevId);
if (prev != null) {
if (prev.getValueMap(property).containsKey(revision)) {
@@ -824,7 +910,8 @@ public final class NodeDocument extends
public NodeDocument apply(Map.Entry<Revision, Range> input) {
if (input.getValue().includes(revision)) {
Revision r = input.getKey();
- String prevId = Utils.getPreviousIdFor(getId(), r);
+ int h = input.getValue().height;
+ String prevId = Utils.getPreviousIdFor(mainPath, r, h);
NodeDocument prev = store.find(Collection.NODES,
prevId);
if (prev != null) {
return prev;
@@ -881,7 +968,7 @@ public final class NodeDocument extends
public static void setChildrenFlag(@Nonnull UpdateOp op,
boolean hasChildNode) {
- checkNotNull(op).set(CHILDREN_FLAG, Boolean.valueOf(hasChildNode));
+ checkNotNull(op).set(CHILDREN_FLAG, hasChildNode);
}
public static void setModified(@Nonnull UpdateOp op,
@@ -957,10 +1044,14 @@ public final class NodeDocument extends
}
public static void setPrevious(@Nonnull UpdateOp op,
- @Nonnull Revision high,
- @Nonnull Revision low) {
- checkNotNull(op).setMapEntry(PREVIOUS, checkNotNull(high),
- checkNotNull(low).toString());
+ @Nonnull Range range) {
+ checkNotNull(op).setMapEntry(PREVIOUS, checkNotNull(range).high,
+ range.getLowValue());
+ }
+
+ public static void removePrevious(@Nonnull UpdateOp op,
+ @Nonnull Range range) {
+ checkNotNull(op).removeMapEntry(PREVIOUS, checkNotNull(range).high);
}
public static void setHasBinary(@Nonnull UpdateOp op) {
@@ -1192,11 +1283,6 @@ public final class NodeDocument extends
return ValueMap.create(this, DELETED);
}
- @Nonnull
- private Map<Revision, String> getCommitRoot() {
- return ValueMap.create(this, COMMIT_ROOT);
- }
-
/**
* The list of children for a node. The list might be complete or not, in
* which case it only represents a block of children.
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PropertyHistory.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PropertyHistory.java?rev=1582045&r1=1582044&r2=1582045&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PropertyHistory.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PropertyHistory.java
Wed Mar 26 20:34:36 2014
@@ -46,29 +46,30 @@ class PropertyHistory implements Iterabl
private static final Logger LOG =
LoggerFactory.getLogger(PropertyHistory.class);
private final DocumentStore store;
- private final NodeDocument main;
+ private final NodeDocument doc;
private final String property;
- private final String id;
+ // path of the main document
+ private final String mainPath;
public PropertyHistory(@Nonnull DocumentStore store,
- @Nonnull NodeDocument main,
+ @Nonnull NodeDocument doc,
@Nonnull String property) {
this.store = checkNotNull(store);
- this.main = checkNotNull(main);
+ this.doc = checkNotNull(doc);
this.property = checkNotNull(property);
- this.id = main.getId();
+ this.mainPath = doc.getMainPath();
}
@Override
public Iterator<NodeDocument> iterator() {
- return ensureOrder(filter(
- transform(main.getPreviousRanges().entrySet(),
- new Function<Map.Entry<Revision, Range>,
Map.Entry<Revision, NodeDocument>>() {
+ return ensureOrder(filter(transform(doc.getPreviousRanges().entrySet(),
+ new Function<Map.Entry<Revision, Range>, Map.Entry<Revision,
NodeDocument>>() {
@Nullable
@Override
public Map.Entry<Revision, NodeDocument> apply(Map.Entry<Revision,
Range> input) {
Revision r = input.getKey();
- String prevId = Utils.getPreviousIdFor(id, r);
+ int h = input.getValue().height;
+ String prevId = Utils.getPreviousIdFor(mainPath, r, h);
NodeDocument prev = store.find(Collection.NODES, prevId);
if (prev == null) {
LOG.warn("Document with previous revisions not found: " +
prevId);
@@ -155,10 +156,11 @@ class PropertyHistory implements Iterabl
// check if the revision is actually in there
if (doc != null) {
Map<Revision, String> values =
doc.getValueMap(property);
- if (!values.isEmpty()) {
+ Iterator<Revision> revs = values.keySet().iterator();
+ if (revs.hasNext()) {
// put into queue with first (highest) revision
// from value map
- queue.put(values.keySet().iterator().next(), doc);
+ queue.put(revs.next(), doc);
}
}
}
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Range.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Range.java?rev=1582045&r1=1582044&r2=1582045&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Range.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Range.java
Wed Mar 26 20:34:36 2014
@@ -28,6 +28,7 @@ final class Range {
final Revision high;
final Revision low;
+ final int height;
/**
* A range of revisions, with both inclusive bounds.
@@ -35,13 +36,49 @@ final class Range {
* @param high the high bound.
* @param low the low bound.
*/
- Range(@Nonnull Revision high, @Nonnull Revision low) {
+ Range(@Nonnull Revision high, @Nonnull Revision low, int height) {
this.high = checkNotNull(high);
this.low = checkNotNull(low);
+ this.height = height;
checkArgument(high.getClusterId() == low.getClusterId(),
"Revisions from have the same clusterId");
checkArgument(high.compareRevisionTime(low) >= 0,
"High Revision must be later than low Revision, high=" + high
+ " low=" + low);
+ checkArgument(height >= 0);
+ }
+
+ /**
+ * Creates a {@code Range} from a revisioned document entry.
+ *
+ * @param rev the revision of the entry corresponding to the high bound
+ * of the range.
+ * @param value the string representation of the lower bound with the
height
+ * (e.g. r1-0-1/0).
+ * @return the range.
+ */
+ @Nonnull
+ static Range fromEntry(Revision rev, String value) {
+ Revision low;
+ int height;
+ int idx = value.indexOf('/');
+ if (idx == -1) {
+ // backward compatibility for lower bound without height
+ low = Revision.fromString(value);
+ height = 0;
+ } else {
+ low = Revision.fromString(value.substring(0, idx));
+ height = Integer.parseInt(value.substring(idx + 1));
+ }
+ return new Range(rev, low, height);
+ }
+
+ /**
+ * @return the string representation of the lower bound, including the
+ * height (e.g. r1-0-1/0).
+ */
+ @Nonnull
+ String getLowValue() {
+ return low + "/" + height;
}
/**
@@ -57,8 +94,34 @@ final class Range {
&& low.compareRevisionTime(r) <= 0;
}
+ /**
+ * Returns the height of this range in the tree of previous documents. The
+ * range of a leaf document has height zero.
+ *
+ * @return the height.
+ */
+ int getHeight() {
+ return height;
+ }
+
@Override
public String toString() {
- return low.toString();
+ return high.toString() + " : " + low.toString() + "/" + height;
+ }
+
+ @Override
+ public int hashCode() {
+ return high.hashCode() ^ low.hashCode() ^ height;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Range) {
+ Range other = (Range) obj;
+ return high.equals(other.high)
+ && low.equals(other.low)
+ && height == other.height;
+ }
+ return false;
}
}
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ValueMap.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ValueMap.java?rev=1582045&r1=1582044&r2=1582045&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ValueMap.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ValueMap.java
Wed Mar 26 20:34:36 2014
@@ -30,6 +30,7 @@ import javax.annotation.Nonnull;
import org.apache.jackrabbit.oak.plugins.document.util.MergeSortedIterators;
+import com.google.common.base.Objects;
import com.google.common.collect.Iterators;
/**
@@ -39,25 +40,32 @@ import com.google.common.collect.Iterato
class ValueMap {
static final SortedMap<Revision, String> EMPTY =
Collections.unmodifiableSortedMap(
- new TreeMap<Revision, String>(StableRevisionComparator.INSTANCE));
+ new TreeMap<Revision, String>(StableRevisionComparator.REVERSE));
@Nonnull
static Map<Revision, String> create(@Nonnull final NodeDocument doc,
- @Nonnull final String property) {
+ @Nonnull final String property) {
final SortedMap<Revision, String> map = doc.getLocalMap(property);
if (doc.getPreviousRanges().isEmpty()) {
return map;
}
- final Comparator<? super Revision> c = map.comparator();
- final Iterator<NodeDocument> docs = Iterators.concat(
- Iterators.singletonIterator(doc),
- doc.getPreviousDocs(property, null).iterator());
final Set<Map.Entry<Revision, String>> entrySet
= new AbstractSet<Map.Entry<Revision, String>>() {
@Override
@Nonnull
public Iterator<Map.Entry<Revision, String>> iterator() {
+
+ final Comparator<? super Revision> c = map.comparator();
+ final Iterator<NodeDocument> docs;
+ if (map.isEmpty()) {
+ docs = doc.getPreviousDocs(property, null).iterator();
+ } else {
+ docs = Iterators.concat(
+ Iterators.singletonIterator(doc),
+ doc.getPreviousDocs(property, null).iterator());
+ }
+
return new MergeSortedIterators<Map.Entry<Revision, String>>(
new Comparator<Map.Entry<Revision, String>>() {
@Override
@@ -69,7 +77,18 @@ class ValueMap {
) {
@Override
public Iterator<Map.Entry<Revision, String>>
nextIterator() {
- return docs.hasNext() ?
docs.next().getLocalMap(property).entrySet().iterator() : null;
+ NodeDocument d = docs.hasNext() ? docs.next() : null;
+ if (d == null) {
+ return null;
+ }
+ Map<Revision, String> values;
+ if (Objects.equal(d.getId(), doc.getId())) {
+ // return local map for main document
+ values = d.getLocalMap(property);
+ } else {
+ values = d.getValueMap(property);
+ }
+ return values.entrySet().iterator();
}
@Override
@@ -83,7 +102,7 @@ class ValueMap {
public int size() {
int size = map.size();
for (NodeDocument prev : doc.getPreviousDocs(property, null)) {
- size += prev.getLocalMap(property).size();
+ size += prev.getValueMap(property).size();
}
return size;
}
@@ -108,7 +127,7 @@ class ValueMap {
}
Revision r = (Revision) key;
for (NodeDocument prev : doc.getPreviousDocs(property, r)) {
- value = prev.getLocalMap(property).get(key);
+ value = prev.getValueMap(property).get(key);
if (value != null) {
return value;
}
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java?rev=1582045&r1=1582044&r2=1582045&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java
Wed Mar 26 20:34:36 2014
@@ -283,23 +283,23 @@ public class Utils {
return id.substring(index + 1);
}
- public static String getPreviousIdFor(String id, Revision r) {
- StringBuilder sb = new StringBuilder(id.length() + REVISION_LENGTH +
3);
- int index = id.indexOf(':');
- int depth = 0;
- for (int i = 0; i < index; i++) {
- depth *= 10;
- depth += Character.digit(id.charAt(i), 10);
+ public static String getPreviousPathFor(String path, Revision r, int
height) {
+ if (!PathUtils.isAbsolute(path)) {
+ throw new IllegalArgumentException("path must be absolute: " +
path);
}
- sb.append(depth + 1).append(":p");
- sb.append(id, index + 1, id.length());
+ StringBuilder sb = new StringBuilder(path.length() + REVISION_LENGTH +
3);
+ sb.append("p").append(path);
if (sb.charAt(sb.length() - 1) != '/') {
sb.append('/');
}
- r.toStringBuilder(sb);
+ r.toStringBuilder(sb).append("/").append(height);
return sb.toString();
}
+ public static String getPreviousIdFor(String path, Revision r, int height)
{
+ return getIdFromPath(getPreviousPathFor(path, r, height));
+ }
+
/**
* Deep copy of a map that may contain map values.
*
Modified:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentSplitTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentSplitTest.java?rev=1582045&r1=1582044&r2=1582045&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentSplitTest.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentSplitTest.java
Wed Mar 26 20:34:36 2014
@@ -24,11 +24,14 @@ import java.util.Map;
import java.util.Random;
import java.util.Set;
+import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.spi.blob.MemoryBlobStore;
import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
import org.apache.jackrabbit.oak.plugins.document.util.Utils;
import org.junit.Test;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
@@ -38,7 +41,7 @@ import static org.junit.Assert.assertNul
import static org.junit.Assert.assertTrue;
/**
- * Check correct splitting of documents (OAK-926).
+ * Check correct splitting of documents (OAK-926 & OAK-1342).
*/
public class DocumentSplitTest extends BaseDocumentMKTest {
@@ -298,6 +301,97 @@ public class DocumentSplitTest extends B
assertNotNull(node);
}
+ @Test
+ public void cascadingSplit() {
+ cascadingSplit("/test/node");
+ }
+
+ @Test
+ public void cascadingSplitLongPath() {
+ String p = "/";
+ while (!Utils.isLongPath(p)) {
+ p = PathUtils.concat(p, "long-path-element");
+ }
+ cascadingSplit(p);
+ }
+
+ private void cascadingSplit(String path) {
+ // use a store without sync delay
+ mk.dispose();
+ mk = new DocumentMK.Builder().setAsyncDelay(0).open();
+ DocumentStore store = mk.getDocumentStore();
+ DocumentNodeStore ns = mk.getNodeStore();
+
+ String rev = null;
+ String p = "/";
+ for (String name : PathUtils.elements(path)) {
+ rev = mk.commit(p, "+\"" + name + "\":{}", rev, null);
+ p = PathUtils.concat(p, name);
+ }
+
+ List<String> revs = Lists.newArrayList();
+ for (int i = 0; i < NodeDocument.PREV_SPLIT_FACTOR + 1; i++) {
+ NodeDocument doc = store.find(NODES, Utils.getIdFromPath(path));
+ assertNotNull(doc);
+ assertEquals(i, doc.getPreviousRanges().size());
+ for (int j = 0; j < NodeDocument.NUM_REVS_THRESHOLD; j++) {
+ int value = (i * NodeDocument.NUM_REVS_THRESHOLD + j);
+ rev = mk.commit(path, "^\"prop\":" + value, rev, null);
+ revs.add(rev);
+ }
+ ns.runBackgroundOperations();
+ }
+ NodeDocument doc = store.find(NODES, Utils.getIdFromPath(path));
+ assertNotNull(doc);
+ assertEquals(2, doc.getPreviousRanges().size());
+ for (String s : revs) {
+ Revision r = Revision.fromString(s);
+ if (doc.getLocalRevisions().containsKey(r)) {
+ continue;
+ }
+ Iterable<NodeDocument> prev = doc.getPreviousDocs("prop", r);
+ assertEquals(1, Iterables.size(prev));
+ for (NodeDocument d : prev) {
+ assertTrue(d.containsRevision(r));
+ }
+ }
+
+ int numPrev = 0;
+ for (NodeDocument prev : doc.getPreviousDocs("prop", null)) {
+ numPrev++;
+ assertTrue(!prev.getValueMap("prop").isEmpty());
+ }
+ assertEquals(2, numPrev);
+
+ Revision previous = null;
+ int numValues = 0;
+ Map<Revision, String> valueMap = doc.getValueMap("prop");
+ for (Map.Entry<Revision, String> entry : valueMap.entrySet()) {
+ if (previous != null) {
+ assertTrue(ns.isRevisionNewer(previous, entry.getKey()));
+ }
+ previous = entry.getKey();
+ numValues++;
+ assertEquals(entry.getValue(), valueMap.get(entry.getKey()));
+ }
+ assertEquals(revs.size(), numValues);
+ assertEquals(revs.size(), valueMap.size());
+
+ assertNotNull(doc.getNodeAtRevision(ns, Revision.fromString(rev),
null));
+ }
+
+ @Test
+ public void mainPath() {
+ Revision r = Revision.fromString("r1-0-1");
+ for (String path : new String[]{"/", "/test", "/test/path"}) {
+ DocumentStore store = mk.getDocumentStore();
+ NodeDocument doc = new NodeDocument(store);
+ String id = Utils.getPreviousIdFor(path, r, 0);
+ doc.put(NodeDocument.ID, id);
+ assertEquals(path, doc.getMainPath());
+ }
+ }
+
private void syncMKs(List<DocumentMK> mks, int idx) {
mks.get(idx).runBackgroundOperations();
for (int i = 0; i < mks.size(); i++) {
Modified:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RangeTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RangeTest.java?rev=1582045&r1=1582044&r2=1582045&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RangeTest.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RangeTest.java
Wed Mar 26 20:34:36 2014
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.plugin
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -32,7 +33,7 @@ public class RangeTest {
Revision r = new Revision(0x200, 0, 1);
Revision other = new Revision(0x200, 0, 2);
- Range range = new Range(high, low);
+ Range range = new Range(high, low, 0);
// bounds are inclusive
assertTrue(range.includes(low));
@@ -45,4 +46,13 @@ public class RangeTest {
// other cluster id
assertFalse(range.includes(other));
}
+
+ @Test
+ public void parse() {
+ Revision low = Revision.fromString("r1-0-1");
+ Revision high = Revision.fromString("r2-0-1");
+ Range r = new Range(high, low, 0);
+ assertEquals("r1-0-1/0", r.getLowValue());
+ assertEquals(r, Range.fromEntry(high, r.getLowValue()));
+ }
}
Modified:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ValueMapTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ValueMapTest.java?rev=1582045&r1=1582044&r2=1582045&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ValueMapTest.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ValueMapTest.java
Wed Mar 26 20:34:36 2014
@@ -41,18 +41,19 @@ public class ValueMapTest {
@Test
public void previousDocs1() {
- String rootId = Utils.getIdFromPath("/");
+ String rootPath = "/";
+ String rootId = Utils.getIdFromPath(rootPath);
Revision r0 = new Revision(0, 0, 1);
MemoryDocumentStore store = new MemoryDocumentStore();
// create previous docs
- UpdateOp op = new UpdateOp(Utils.getPreviousIdFor(rootId, r0), true);
+ UpdateOp op = new UpdateOp(Utils.getPreviousIdFor(rootPath, r0, 0),
true);
op.set(ID, op.getId());
op.setMapEntry("prop", r0, "0");
NodeDocument.setRevision(op, r0, "c");
store.createOrUpdate(NODES, op);
Revision r1low = new Revision(1, 0, 1);
Revision r1high = new Revision(1, 10, 1);
- op = new UpdateOp(Utils.getPreviousIdFor(rootId, r1high), true);
+ op = new UpdateOp(Utils.getPreviousIdFor(rootPath, r1high, 0), true);
op.set(ID, op.getId());
for (int i = r1low.getCounter(); i <= r1high.getCounter(); i++) {
Revision r = new Revision(1, i, 1);
@@ -66,8 +67,8 @@ public class ValueMapTest {
Revision r2 = new Revision(2, 0, 1);
op.setMapEntry("prop", r2, "1");
NodeDocument.setRevision(op, r2, "c");
- NodeDocument.setPrevious(op, r0, r0);
- NodeDocument.setPrevious(op, r1high, r1low);
+ NodeDocument.setPrevious(op, new Range(r0, r0, 0));
+ NodeDocument.setPrevious(op, new Range(r1high, r1low, 0));
store.createOrUpdate(NODES, op);
NodeDocument doc = store.find(NODES, rootId);
@@ -86,7 +87,8 @@ public class ValueMapTest {
@Test
public void previousDocs2() {
MemoryDocumentStore store = new MemoryDocumentStore();
- String rootId = Utils.getIdFromPath("/");
+ String rootPath = "/";
+ String rootId = Utils.getIdFromPath(rootPath);
Revision r01 = new Revision(0, 0, 1);
Revision r12 = new Revision(1, 0, 2);
Revision r22 = new Revision(2, 0, 2);
@@ -94,7 +96,7 @@ public class ValueMapTest {
Revision r42 = new Revision(4, 0, 2);
Revision r51 = new Revision(5, 0, 1);
// create previous docs
- UpdateOp op = new UpdateOp(Utils.getPreviousIdFor(rootId, r31), true);
+ UpdateOp op = new UpdateOp(Utils.getPreviousIdFor(rootPath, r31, 0),
true);
op.set(ID, op.getId());
op.setMapEntry("p0", r01, "0");
NodeDocument.setRevision(op, r01, "c");
@@ -102,7 +104,7 @@ public class ValueMapTest {
NodeDocument.setRevision(op, r31, "c");
store.createOrUpdate(NODES, op);
- op = new UpdateOp(Utils.getPreviousIdFor(rootId, r42), true);
+ op = new UpdateOp(Utils.getPreviousIdFor(rootPath, r42, 0), true);
op.set(ID, op.getId());
op.setMapEntry("p1", r12, "0");
NodeDocument.setRevision(op, r12, "c");
@@ -118,8 +120,8 @@ public class ValueMapTest {
op.setMapEntry("p0", r51, "2");
op.setMapEntry("p1", r51, "2");
NodeDocument.setRevision(op, r51, "c");
- NodeDocument.setPrevious(op, r42, r12);
- NodeDocument.setPrevious(op, r31, r01);
+ NodeDocument.setPrevious(op, new Range(r42, r12, 0));
+ NodeDocument.setPrevious(op, new Range(r31, r01, 0));
store.createOrUpdate(NODES, op);
NodeDocument doc = store.find(NODES, rootId);
@@ -127,8 +129,8 @@ public class ValueMapTest {
List<NodeDocument> prevDocs = Lists.newArrayList(
doc.getPreviousDocs("p1", null));
assertEquals(2, prevDocs.size());
- assertEquals(Utils.getPreviousIdFor(rootId, r31),
prevDocs.get(0).getId());
- assertEquals(Utils.getPreviousIdFor(rootId, r42),
prevDocs.get(1).getId());
+ assertEquals(Utils.getPreviousIdFor(rootPath, r31, 0),
prevDocs.get(0).getId());
+ assertEquals(Utils.getPreviousIdFor(rootPath, r42, 0),
prevDocs.get(1).getId());
List<Revision> revs = new ArrayList<Revision>();
for (Revision r : doc.getValueMap("p1").keySet()) {
Modified:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java?rev=1582045&r1=1582044&r2=1582045&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java
Wed Mar 26 20:34:36 2014
@@ -30,26 +30,26 @@ public class UtilsTest {
@Test
public void getPreviousIdFor() {
Revision r = new Revision(System.currentTimeMillis(), 0, 0);
- String p = Utils.getIdFromPath("/");
- assertEquals("1:p/" + r.toString(), Utils.getPreviousIdFor(p, r));
- p = Utils.getIdFromPath("/test");
- assertEquals("2:p/test/" + r.toString(), Utils.getPreviousIdFor(p, r));
- p = Utils.getIdFromPath("/a/b/c/d/e/f/g/h/i/j/k/l/m");
- assertEquals("14:p/a/b/c/d/e/f/g/h/i/j/k/l/m/" + r.toString(),
Utils.getPreviousIdFor(p, r));
+ assertEquals("2:p/" + r.toString() + "/0",
+ Utils.getPreviousIdFor("/", r, 0));
+ assertEquals("3:p/test/" + r.toString() + "/1",
+ Utils.getPreviousIdFor("/test", r, 1));
+ assertEquals("15:p/a/b/c/d/e/f/g/h/i/j/k/l/m/" + r.toString() + "/3",
+ Utils.getPreviousIdFor("/a/b/c/d/e/f/g/h/i/j/k/l/m", r, 3));
}
@Ignore("Performance test")
@Test
public void performance_getPreviousIdFor() {
Revision r = new Revision(System.currentTimeMillis(), 0, 0);
- String id = Utils.getIdFromPath("/some/test/path/foo");
+ String path = "/some/test/path/foo";
// warm up
for (int i = 0; i < 1 * 1000 * 1000; i++) {
- Utils.getPreviousIdFor(id, r);
+ Utils.getPreviousIdFor(path, r, 0);
}
long time = System.currentTimeMillis();
for (int i = 0; i < 10 * 1000 * 1000; i++) {
- Utils.getPreviousIdFor(id, r);
+ Utils.getPreviousIdFor(path, r, 0);
}
time = System.currentTimeMillis() - time;
System.out.println(time);