in oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java [212:333]
long count(Filter filter, NodeState root, NodeState indexMeta, final String indexStorageNodeName,
Set<String> values, int max) {
NodeState index = indexMeta.getChildNode(indexStorageNodeName);
long count = -1;
if (values == null) {
// property is not null
PropertyState ec = indexMeta.getProperty(ENTRY_COUNT_PROPERTY_NAME);
if (ec != null) {
// negative value implies fall-back to counting
count = ec.getValue(Type.LONG);
} else {
// negative value means that approximation isn't available
count = ApproximateCounter.getCountSync(index);
}
if (count < 0) {
CountingNodeVisitor v = new CountingNodeVisitor(max);
v.visit(index);
count = v.getEstimatedCount();
if (count >= max) {
// "is not null" queries typically read more data
count *= 10;
}
}
} else {
// property = x, or property in (x, y, z)
int size = values.size();
if (size == 0) {
return 0;
}
PropertyState ec = indexMeta.getProperty(ENTRY_COUNT_PROPERTY_NAME);
if (ec != null) {
count = ec.getValue(Type.LONG);
if (count >= 0) {
// assume 10*NodeCounterEditor.DEFAULT_RESOLUTION entries per key, so that this index is used
// instead of traversal, but not instead of a regular property index
long keyCount = count / (10 * NodeCounterEditor.DEFAULT_RESOLUTION);
ec = indexMeta.getProperty(KEY_COUNT_PROPERTY_NAME);
if (ec != null) {
keyCount = ec.getValue(Type.LONG);
}
// cast to double to avoid overflow
// (entryCount could be Long.MAX_VALUE)
// the cost is not multiplied by the size,
// otherwise the traversing index might be used
keyCount = Math.max(1, keyCount);
count = (long) ((double) count / keyCount) + size;
}
} else {
// for this index, property "entryCount" is not set
long approxMax = 0;
long approxCount = ApproximateCounter.getCountSync(index);
if (approxCount != -1) {
// approximate count is available for the index:
// check approximate counts for each value
for (String p : values) {
NodeState s = index.getChildNode(p);
if (s.exists()) {
long a = ApproximateCounter.getCountSync(s);
if (a != -1) {
approxMax += a;
} else if (approxMax > 0) {
// in absence of approx count for a key we should be conservative
approxMax += 10 * NodeCounterEditor.DEFAULT_RESOLUTION;
}
}
}
if (approxMax > 0) {
count = approxMax;
}
}
}
// still, property = x, or property in (x, y, z),
// and we don't know the count ("entryCount" = -1)
if (count < 0) {
count = 0;
max = Math.max(10, max / size);
int i = 0;
for (String p : values) {
if (count > max && i > 3) {
// the total count is extrapolated from the the number
// of values counted so far to the total number of values
count = count * size / i;
break;
}
NodeState s = index.getChildNode(p);
if (s.exists()) {
CountingNodeVisitor v = new CountingNodeVisitor(max);
v.visit(s);
count += v.getEstimatedCount();
}
i++;
}
}
}
String filterRootPath = null;
if (filter != null &&
filter.getPathRestriction().equals(Filter.PathRestriction.ALL_CHILDREN)) {
filterRootPath = filter.getPath();
}
if (filterRootPath != null) {
// scale cost according to path restriction
long totalNodesCount = NodeCounter.getEstimatedNodeCount(root, "/", true);
if (totalNodesCount != -1) {
long filterPathCount = NodeCounter.getEstimatedNodeCount(root, filterRootPath, true);
if (filterPathCount != -1) {
// assume nodes in the index are evenly distributed in the repository (old idea)
long countScaledDown = (long) ((double) count / totalNodesCount * filterPathCount);
// assume 80% of the indexed nodes are in this subtree
long mostNodesFromThisSubtree = (long) (filterPathCount * 0.8);
// count can at most be the assumed subtree size
count = Math.min(count, mostNodesFromThisSubtree);
// this in theory should not have any effect,
// except if the above estimates are incorrect,
// so this is just for safety feature
count = Math.max(count, countScaledDown);
}
}
}
return count;
}