diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/StatisticalSortedSetDocValuesFacetCounts.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/StatisticalSortedSetDocValuesFacetCounts.java index 34bb06c..d7d2a78 100644 --- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/StatisticalSortedSetDocValuesFacetCounts.java +++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/StatisticalSortedSetDocValuesFacetCounts.java @@ -52,12 +52,14 @@ class StatisticalSortedSetDocValuesFacetCounts extends SortedSetDocValuesFacetCo private final Filter filter; private final IndexReader reader; private final SecureFacetConfiguration secureFacetConfiguration; + private final DefaultSortedSetDocValuesReaderState state; private FacetResult facetResult = null; StatisticalSortedSetDocValuesFacetCounts(DefaultSortedSetDocValuesReaderState state, FacetsCollector facetsCollector, Filter filter, SecureFacetConfiguration secureFacetConfiguration) throws IOException { super(state, facetsCollector); + this.state = state; this.reader = state.origReader; this.facetsCollector = facetsCollector; this.filter = filter; @@ -89,6 +91,13 @@ class StatisticalSortedSetDocValuesFacetCounts extends SortedSetDocValuesFacetCo hitCount += matchingDocs.totalHits; } int sampleSize = secureFacetConfiguration.getStatisticalFacetSampleSize(); + // In case the hit count is less than sample size(A very small reposiotry perhaps) + // Delegate getting FacetResults to SecureSortedSetDocValuesFacetCounts to get the exact count + // instead of statiscal count. + if (hitCount < sampleSize) { + return new SecureSortedSetDocValuesFacetCounts(state, facetsCollector, filter).getTopChildren(topN, dim, path); + } + long randomSeed = secureFacetConfiguration.getRandomSeed(); LOG.debug("Sampling facet dim {}; hitCount: {}, sampleSize: {}, seed: {}", dim, hitCount, sampleSize, randomSeed); diff --git a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SecureFacetTest.java b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SecureFacetTest.java index 34db603..09699d3 100644 --- a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SecureFacetTest.java +++ b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SecureFacetTest.java @@ -136,7 +136,7 @@ public class SecureFacetTest { Node par = allow(getOrCreateByPath("/parent", "oak:Unstructured", session)); - for (int i = 0; i < 4; i++) { + for (int i = 0; i < NUM_LABELS; i++) { Node subPar = par.addNode("par" + i); for (int j = 0; j < NUM_LEAF_NODES; j++) { Node child = subPar.addNode("c" + j); @@ -172,6 +172,51 @@ public class SecureFacetTest { assertNotEquals("Acl-ed and actual counts mustn't be same", actualLabelCount, actualAclLabelCount); } + private void createSmallDataset() throws RepositoryException{ + Random rGen = new Random(42); + int[] labelCount = new int[NUM_LABELS]; + int[] aclLabelCount = new int[NUM_LABELS]; + int[] aclPar1LabelCount = new int[NUM_LABELS]; + + Node par = allow(getOrCreateByPath("/parent", "oak:Unstructured", session)); + + for (int i = 0; i < NUM_LABELS; i++) { + Node subPar = par.addNode("par" + i); + for (int j = 0; j < NUM_LEAF_NODES/(2 * NUM_LABELS); j++) { + Node child = subPar.addNode("c" + j); + child.setProperty("cons", "val"); + + // Add a random label out of "l0", "l1", "l2", "l3" + int labelNum = rGen.nextInt(NUM_LABELS); + child.setProperty("foo", "l" + labelNum); + + labelCount[labelNum]++; + if (i != 0) { + aclLabelCount[labelNum]++; + } + if (i == 1) { + aclPar1LabelCount[labelNum]++; + } + } + + // deny access for one sub-parent + if (i == 0) { + deny(subPar); + } + } + + session.save(); + + for (int i = 0; i < labelCount.length; i++) { + actualLabelCount.put("l" + i, labelCount[i]); + actualAclLabelCount.put("l" + i, aclLabelCount[i]); + actualAclPar1LabelCount.put("l" + i, aclPar1LabelCount[i]); + } + + assertNotEquals("Acl-ed and actual counts mustn't be same", actualLabelCount, actualAclLabelCount); + + } + @Test public void secureFacets() throws Exception { createLargeDataset(); @@ -225,6 +270,28 @@ public class SecureFacetTest { } @Test + public void statiscalFacetsWithHitCountLessThanSampleSize() throws Exception { + Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + FACETS, "nt:unstructured", session); + facetConfig.setProperty(PROP_SECURE_FACETS, PROP_SECURE_FACETS_VALUE_STATISTICAL); + indexNode.setProperty(PROP_REFRESH_DEFN, true); + session.save(); + + createSmallDataset(); + + Map facets = getFacets(); + assertEquals("Unexpected number of facets", actualAclLabelCount.size(), facets.size()); + + for (Map.Entry facet : actualAclLabelCount.entrySet()) { + String facetLabel = facet.getKey(); + int facetCount = facets.get(facetLabel); + + // Since the hit count is less than sample size -> flow should have switched to secure facet count instead of statistical + // and thus the count should be exactly similar + assertEquals(facetCount,(int)facet.getValue()); + } + } + + @Test public void statisticalFacets_withHitCountSameAsSampleSize() throws Exception { Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + FACETS, "nt:unstructured", session); facetConfig.setProperty(PROP_SECURE_FACETS, PROP_SECURE_FACETS_VALUE_STATISTICAL);