Author: catholicon
Date: Fri Nov 23 09:27:31 2018
New Revision: 1847231

URL: http://svn.apache.org/viewvc?rev=1847231&view=rev
Log:
OAK-7898: Facet queries with UNION should do trivial merge of facets from 
sub-queries (backport r1846617 from trunk)


Added:
    
jackrabbit/oak/branches/1.8/oak-core/src/test/java/org/apache/jackrabbit/oak/query/ResultRowImplTest.java
      - copied unchanged from r1846617, 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/ResultRowImplTest.java
Modified:
    jackrabbit/oak/branches/1.8/   (props changed)
    
jackrabbit/oak/branches/1.8/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultRowImpl.java
    
jackrabbit/oak/branches/1.8/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java
    
jackrabbit/oak/branches/1.8/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
    
jackrabbit/oak/branches/1.8/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/query/facet/FacetResult.java
    
jackrabbit/oak/branches/1.8/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/query/facet/package-info.java
    
jackrabbit/oak/branches/1.8/oak-query-spi/src/test/java/org/apache/jackrabbit/oak/query/facet/FacetResultTest.java

Propchange: jackrabbit/oak/branches/1.8/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Fri Nov 23 09:27:31 2018
@@ -1,3 +1,3 @@
 /jackrabbit/oak/branches/1.0:1665962
-/jackrabbit/oak/trunk:1820660-1820661,1820729,1820734,1820859,1820861,1820878,1820888,1820947,1821027,1821130,1821140-1821141,1821178,1821237,1821240,1821249,1821258,1821325,1821358,1821361-1821362,1821370,1821375,1821393,1821477,1821487,1821516,1821617,1821663,1821665,1821668,1821681,1821847,1821975-1821983,1822121,1822201,1822207,1822527,1822642,1822723,1822808,1822850,1822934,1823135,1823163,1823169,1823172,1823655,1823669,1824196,1824198,1824253,1824255,1824896,1824962,1825065,1825362,1825381,1825442,1825448,1825466,1825470-1825471,1825475,1825523,1825525,1825561,1825619-1825621,1825651,1825654,1825992,1826079,1826090,1826096,1826216,1826237,1826338,1826516,1826532,1826551,1826560,1826638,1826640,1826730,1826833,1826932,1826957,1827423,1827472,1827486,1827816,1827977,1828349,1828439,1828502,1828529,1828948,1829527,1829534,1829546,1829569,1829587,1829665,1829854,1829864,1829978,1829985,1829987,1829998,1830019,1830048,1830160,1830171,1830197,1830209,1830239,1830347,1830748,1830911
 
,1830923,1831157-1831158,1831163,1831190,1831374,1831560,1831689,1832258,1832376,1832379,1832535,1833308,1833347,1833833,1834112,1834117,1834287,1834291,1834302,1834326,1834328,1834336,1834428,1834468,1834483,1834610,1834648-1834649,1834681,1834823,1834857-1834858,1835060,1835518,1835521,1835635,1835642,1835780,1835819,1836082,1836121,1836167-1836168,1836170-1836187,1836189-1836196,1836206,1836487,1836493,1837057,1837274,1837296,1837326,1837475,1837503,1837547,1837569,1837600,1837657,1837718,1837998,1838076,1838637,1839549,1839570,1839637,1839746,1840019,1840024,1840031,1840226,1840455,1840462,1840574,1841314,1841352,1842089,1842677,1843175,1843222,1843231,1843398,1843618,1843652,1843911,1844325,1844549,1844625,1844627,1844642,1844728,1844775,1844932,1845135,1845336,1845405,1845415,1845730-1845731,1845863,1845865,1846057
+/jackrabbit/oak/trunk:1820660-1820661,1820729,1820734,1820859,1820861,1820878,1820888,1820947,1821027,1821130,1821140-1821141,1821178,1821237,1821240,1821249,1821258,1821325,1821358,1821361-1821362,1821370,1821375,1821393,1821477,1821487,1821516,1821617,1821663,1821665,1821668,1821681,1821847,1821975-1821983,1822121,1822201,1822207,1822527,1822642,1822723,1822808,1822850,1822934,1823135,1823163,1823169,1823172,1823655,1823669,1824196,1824198,1824253,1824255,1824896,1824962,1825065,1825362,1825381,1825442,1825448,1825466,1825470-1825471,1825475,1825523,1825525,1825561,1825619-1825621,1825651,1825654,1825992,1826079,1826090,1826096,1826216,1826237,1826338,1826516,1826532,1826551,1826560,1826638,1826640,1826730,1826833,1826932,1826957,1827423,1827472,1827486,1827816,1827977,1828349,1828439,1828502,1828529,1828948,1829527,1829534,1829546,1829569,1829587,1829665,1829854,1829864,1829978,1829985,1829987,1829998,1830019,1830048,1830160,1830171,1830197,1830209,1830239,1830347,1830748,1830911
 
,1830923,1831157-1831158,1831163,1831190,1831374,1831560,1831689,1832258,1832376,1832379,1832535,1833308,1833347,1833833,1834112,1834117,1834287,1834291,1834302,1834326,1834328,1834336,1834428,1834468,1834483,1834610,1834648-1834649,1834681,1834823,1834857-1834858,1835060,1835518,1835521,1835635,1835642,1835780,1835819,1836082,1836121,1836167-1836168,1836170-1836187,1836189-1836196,1836206,1836487,1836493,1837057,1837274,1837296,1837326,1837475,1837503,1837547,1837569,1837600,1837657,1837718,1837998,1838076,1838637,1839549,1839570,1839637,1839746,1840019,1840024,1840031,1840226,1840455,1840462,1840574,1841314,1841352,1842089,1842677,1843175,1843222,1843231,1843398,1843618,1843652,1843911,1844325,1844549,1844625,1844627,1844642,1844728,1844775,1844932,1845135,1845336,1845405,1845415,1845730-1845731,1845863,1845865,1846057,1846617
 /jackrabbit/trunk:1345480

Modified: 
jackrabbit/oak/branches/1.8/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultRowImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.8/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultRowImpl.java?rev=1847231&r1=1847230&r2=1847231&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.8/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultRowImpl.java
 (original)
+++ 
jackrabbit/oak/branches/1.8/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultRowImpl.java
 Fri Nov 23 09:27:31 2018
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.query;
 
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Map;
 
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.api.PropertyValue;
@@ -264,4 +265,16 @@ public class ResultRowImpl implements Re
 
     }
 
+    static ResultRowImpl getMappingResultRow(ResultRowImpl delegate, final 
Map<String, String> columnToFacetMap) {
+        if (columnToFacetMap.size() == 0) {
+            return delegate;
+        }
+
+        PropertyValue[] mappedVals = delegate.getValues();
+        for (Map.Entry<String, String> entry : columnToFacetMap.entrySet()) {
+            mappedVals[delegate.query.getColumnIndex(entry.getKey())]   = 
PropertyValues.newString(entry.getValue());
+        }
+        return new ResultRowImpl(delegate.query, delegate.trees, mappedVals,
+                delegate.distinctValues, delegate.orderValues);
+    }
 }
\ No newline at end of file

Modified: 
jackrabbit/oak/branches/1.8/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.8/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java?rev=1847231&r1=1847230&r2=1847231&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.8/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java
 (original)
+++ 
jackrabbit/oak/branches/1.8/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java
 Fri Nov 23 09:27:31 2018
@@ -21,22 +21,27 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import com.google.common.collect.AbstractIterator;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Maps;
+import com.google.common.collect.PeekingIterator;
 import org.apache.jackrabbit.oak.api.PropertyValue;
 import org.apache.jackrabbit.oak.api.Result;
 import org.apache.jackrabbit.oak.api.Result.SizePrecision;
+import org.apache.jackrabbit.oak.api.ResultRow;
 import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.plugins.memory.PropertyValues;
 import org.apache.jackrabbit.oak.query.QueryImpl.MeasuringIterator;
 import org.apache.jackrabbit.oak.query.ast.ColumnImpl;
 import org.apache.jackrabbit.oak.query.ast.OrderingImpl;
+import org.apache.jackrabbit.oak.query.facet.FacetResult;
 import 
org.apache.jackrabbit.oak.query.stats.QueryStatsData.QueryExecutionStats;
+import org.apache.jackrabbit.oak.spi.query.QueryConstants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterators;
-import com.google.common.collect.Maps;
-
 /**
  * Represents a union query.
  */
@@ -295,9 +300,11 @@ public class UnionQueryImpl implements Q
         boolean distinct = !unionAll;
         Comparator<ResultRowImpl> orderBy = 
ResultRowImpl.getComparator(orderings);
 
+        FacetMerger facetMerger = new FacetMerger(left, right);
+
         Iterator<ResultRowImpl> it;
-        final Iterator<ResultRowImpl> leftRows = left.getRows();
-        final Iterator<ResultRowImpl> rightRows = right.getRows();
+        final Iterator<ResultRowImpl> leftRows = 
facetMerger.getLeftIterator();;
+        final Iterator<ResultRowImpl> rightRows = 
facetMerger.getRightIterator();
         Iterator<ResultRowImpl> leftIter = leftRows;
         Iterator<ResultRowImpl> rightIter = rightRows;
 
@@ -412,4 +419,88 @@ public class UnionQueryImpl implements Q
         return left.getQueryExecutionStats();
     }
 
+    static class FacetMerger {
+
+        private final Iterator<ResultRowImpl> leftIterator;
+        private final Iterator<ResultRowImpl> rightIterator;
+
+        FacetMerger(Query left, Query right) {
+            ColumnImpl[] columns = left.getColumns();
+            String[] columnNames = new String[columns.length];
+            Arrays.setAll(columnNames, i -> columns[i].getColumnName());
+
+            Iterator<ResultRowImpl> lIter = left.getRows();
+            Iterator<ResultRowImpl> rIter = right.getRows();
+
+            if (!hasFacets(columnNames) || !bothHaveRows(lIter, rIter)) {
+                this.leftIterator = lIter;
+                this.rightIterator = rIter;
+
+                return;
+            }
+
+            PeekingIterator<ResultRowImpl> lPeekIter = 
Iterators.peekingIterator(lIter);
+            PeekingIterator<ResultRowImpl> rPeekIter = 
Iterators.peekingIterator(rIter);
+
+            ResultRow lRow = lPeekIter.peek();
+            ResultRow rRow = rPeekIter.peek();
+
+            FacetResult facetResult = new FacetResult(columnNames,
+                    columnName -> {
+                        PropertyValue value = lRow.getValue(columnName);
+                        return value == null ? null : 
value.getValue(Type.STRING);
+                    },
+                    columnName -> {
+                        PropertyValue value = rRow.getValue(columnName);
+                        return value == null ? null : 
value.getValue(Type.STRING);
+            });
+
+            Map<String, String> columnToFacetMap = 
facetResult.asColumnToFacetJsonMap();
+
+            this.leftIterator = new MappingRowIterator(columnToFacetMap, 
lPeekIter);
+            this.rightIterator = new MappingRowIterator(columnToFacetMap, 
rPeekIter);
+        }
+
+        Iterator<ResultRowImpl> getLeftIterator() {
+            return leftIterator;
+        }
+
+        Iterator<ResultRowImpl> getRightIterator() {
+            return rightIterator;
+        }
+
+        private boolean hasFacets(String[] columnNames) {
+            for (String c : columnNames) {
+                if (c.startsWith(QueryConstants.REP_FACET + "(")) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private boolean bothHaveRows(Iterator<ResultRowImpl> lIter, 
Iterator<ResultRowImpl> rIter) {
+            return lIter.hasNext() && rIter.hasNext();
+        }
+    }
+
+    static class MappingRowIterator extends AbstractIterator<ResultRowImpl> {
+
+        private final Map<String, String> columnToFacetMap;
+        private final Iterator<ResultRowImpl> delegate;
+
+        MappingRowIterator(Map<String, String> columnToFacetMap, 
Iterator<ResultRowImpl> delegate) {
+            super();
+            this.columnToFacetMap = columnToFacetMap;
+            this.delegate = delegate;
+        }
+
+        @Override
+        protected ResultRowImpl computeNext() {
+            if (delegate.hasNext()) {
+                return ResultRowImpl.getMappingResultRow(delegate.next(), 
columnToFacetMap);
+            } else {
+                return endOfData();
+            }
+        }
+    }
 }

Modified: 
jackrabbit/oak/branches/1.8/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.8/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java?rev=1847231&r1=1847230&r2=1847231&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.8/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
 (original)
+++ 
jackrabbit/oak/branches/1.8/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
 Fri Nov 23 09:27:31 2018
@@ -29,6 +29,7 @@ import javax.jcr.query.RowIterator;
 import javax.jcr.security.Privilege;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import 
org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
@@ -38,6 +39,7 @@ import org.apache.jackrabbit.oak.query.f
 import org.junit.After;
 import org.junit.Before;
 
+import static com.google.common.collect.Sets.newHashSet;
 import static 
org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
 
 /**
@@ -776,6 +778,95 @@ public class FacetTest extends AbstractQ
         assertEquals(2, rows.getSize());
     }
 
+    public void testMergedFacetsOverUnionUniqueLabels() throws Exception {
+        Node n1 = testRootNode.addNode("node1");
+        n1.setProperty("text", "t1");
+        n1.setProperty("x", "x1");
+        n1.setProperty("name","Node1");
+
+        Node n2 = testRootNode.addNode("node2");
+        n2.setProperty("text", "t2");
+        n2.setProperty("x", "x2");
+        n2.setProperty("name","Node2");
+
+        Node n3 = testRootNode.addNode("node3");
+        n3.setProperty("text", "t3");
+        n3.setProperty("x", "x3");
+        n3.setProperty("name","Node3");
+        superuser.save();
+
+        String xpath = "//*[@name = 'Node1' or @text = 't2' or @x = 
'x3']/(rep:facet(text))";
+
+        Query q = qm.createQuery(xpath, Query.XPATH);
+
+        QueryResult result = q.execute();
+        FacetResult facetResult = new FacetResult(result);
+
+        assertEquals("Unexpected dimensions", newHashSet("text"), 
facetResult.getDimensions());
+
+        List<FacetResult.Facet> facets = facetResult.getFacets("text");
+
+        Set<String> facetLabels = newHashSet();
+        for (FacetResult.Facet facet : facets) {
+            assertEquals("Unexpected facet count for " + facet.getLabel(), 1, 
facet.getCount());
+            facetLabels.add(facet.getLabel());
+        }
+
+        assertEquals("Unexpected facet labels", newHashSet("t1", "t2", "t3"), 
facetLabels);
+    }
+
+    public void testMergedFacetsOverUnionSummingCount() throws Exception {
+        // the distribution of nodes with t1 and t2 are intentionally across 
first and second set (below)
+        // put such that second condition turns facet count around
+
+        // first set of nodes matching first condition (x1 = v1)
+        Node n11 = testRootNode.addNode("node11");
+        n11.setProperty("text", "t1");
+        n11.setProperty("x1","v1");
+        Node n12 = testRootNode.addNode("node12");
+        n12.setProperty("text", "t1");
+        n12.setProperty("x1","v1");
+        Node n13 = testRootNode.addNode("node13");
+        n13.setProperty("text", "t2");
+        n13.setProperty("x1","v1");
+
+        // second set of nodes matching second condition (x2 = v2)
+        Node n21 = testRootNode.addNode("node21");
+        n21.setProperty("text", "t2");
+        n21.setProperty("x2","v2");
+        Node n22 = testRootNode.addNode("node22");
+        n22.setProperty("text", "t1");
+        n22.setProperty("x2","v2");
+        Node n23 = testRootNode.addNode("node23");
+        n23.setProperty("text", "t1");
+        n23.setProperty("x2","v2");
+        Node n24 = testRootNode.addNode("node24");
+        n24.setProperty("text", "t1");
+        n24.setProperty("x2","v2");
+
+        superuser.save();
+
+        String xpath = "//*[@x1 = 'v1' or @x2 = 'v2']/(rep:facet(text))";
+
+        Query q = qm.createQuery(xpath, Query.XPATH);
+
+        QueryResult result = q.execute();
+        FacetResult facetResult = new FacetResult(result);
+
+        assertEquals("Unexpected dimensions", newHashSet("text"), 
facetResult.getDimensions());
+
+        List<FacetResult.Facet> facets = facetResult.getFacets("text");
+        assertEquals("Incorrect facet label list size", 2, facets.size());
+
+        FacetResult.Facet facet = facets.get(0);
+        assertEquals("t1", facet.getLabel());
+        assertEquals(5, facet.getCount());
+
+        facet = facets.get(1);
+        assertEquals("t2", facet.getLabel());
+        assertEquals(2, facet.getCount());
+    }
+
     public Node deny(Node node) throws RepositoryException {
         AccessControlUtils.deny(node, "anonymous", Privilege.JCR_ALL);
         return node;

Modified: 
jackrabbit/oak/branches/1.8/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/query/facet/FacetResult.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.8/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/query/facet/FacetResult.java?rev=1847231&r1=1847230&r2=1847231&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.8/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/query/facet/FacetResult.java
 (original)
+++ 
jackrabbit/oak/branches/1.8/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/query/facet/FacetResult.java
 Fri Nov 23 09:27:31 2018
@@ -19,21 +19,27 @@
 
 package org.apache.jackrabbit.oak.query.facet;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
+import org.apache.jackrabbit.oak.commons.json.JsopReader;
+import org.apache.jackrabbit.oak.commons.json.JsopTokenizer;
+import org.apache.jackrabbit.oak.spi.query.QueryConstants;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
 import javax.jcr.Value;
 import javax.jcr.query.QueryResult;
 import javax.jcr.query.Row;
 import javax.jcr.query.RowIterator;
+import java.util.Collections;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.jackrabbit.oak.commons.json.JsopReader;
-import org.apache.jackrabbit.oak.commons.json.JsopTokenizer;
-import org.apache.jackrabbit.oak.spi.query.QueryConstants;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
+import static java.util.Collections.reverseOrder;
+import static java.util.Comparator.comparingInt;
 
 /**
  * A facet result is a wrapper for {@link javax.jcr.query.QueryResult} capable 
of returning information about facets
@@ -48,25 +54,69 @@ public class FacetResult {
             RowIterator rows = queryResult.getRows();
             if (rows.hasNext()) {
                 Row row = rows.nextRow();
-                for (String column : queryResult.getColumnNames()) {
-                    if (column.startsWith(QueryConstants.REP_FACET)) {
-                        String dimension = 
column.substring(QueryConstants.REP_FACET.length() + 1, column.length() - 1);
-                        Value value = row.getValue(column);
-                        if (value != null) {
-                            String jsonFacetString = value.getString();
-                            parseJson(dimension, jsonFacetString);
-                        }
-                    }
-                }
+                parseJson(queryResult.getColumnNames(), columnName -> {
+                    Value value = row.getValue(columnName);
+                    return value == null ? null : value.getString();
+                });
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public FacetResult(String[] columnNames, FacetResultRow...rows) {
+        try {
+            for (FacetResultRow row : rows) {
+                parseJson(columnNames, row);
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
+    public Map<String, String> asColumnToFacetJsonMap() {
+        Map<String, String> json = Maps.newHashMap();
+        for (Map.Entry<String, List<Facet>> entry : perDimFacets.entrySet()) {
+            JsopBuilder builder = new JsopBuilder();
+            builder.object();
+
+            for (Facet f : entry.getValue()) {
+                builder.key(f.getLabel());
+                builder.value(f.getCount());
+            }
+
+            builder.endObject();
+
+            json.put(QueryConstants.REP_FACET + "(" + entry.getKey() + ")", 
builder.toString());
+        }
+
+        return json;
+    }
+
+    private void parseJson(String[] columnNames, FacetResultRow row) throws 
Exception {
+        for (String column : columnNames) {
+            if (column.startsWith(QueryConstants.REP_FACET)) {
+                String dimension = 
column.substring(QueryConstants.REP_FACET.length() + 1, column.length() - 1);
+                String value = row.getValue(column);
+                if (value != null) {
+                    String jsonFacetString = value;
+                    parseJson(dimension, jsonFacetString);
+                }
+            }
+        }
+    }
+
     private void parseJson(String dimension, String jsonFacetString) {
         JsopTokenizer jsopTokenizer = new JsopTokenizer(jsonFacetString);
-        List<Facet> facets = new LinkedList<Facet>();
+        List<Facet> facets = perDimFacets.get(dimension);
+        Map<String, Facet> facetsMap = Maps.newLinkedHashMap();
+        if (facets != null) {
+            for (Facet facet : facets) {
+                if (!facetsMap.containsKey(facet.getLabel())) {
+                    facetsMap.put(facet.getLabel(), facet);
+                }
+            }
+        }
         int c;
         String label = null;
         int count;
@@ -76,11 +126,16 @@ public class FacetResult {
             } else if (JsopReader.NUMBER == c) {
                 count = Integer.parseInt(jsopTokenizer.getEscapedToken());
                 if (label != null) {
-                    facets.add(new Facet(label, count));
+                    if (facetsMap.containsKey(label)) {
+                        count += facetsMap.get(label).getCount();
+                    }
+                    facetsMap.put(label, new Facet(label, count));
                 }
                 label = null;
             }
         }
+        facets = Lists.newArrayList(facetsMap.values());
+        Collections.sort(facets, reverseOrder(comparingInt(Facet::getCount)));
         perDimFacets.put(dimension, facets);
     }
 
@@ -102,7 +157,7 @@ public class FacetResult {
         private final String label;
         private final int count;
 
-        private Facet(String label, int count) {
+        Facet(String label, int count) {
             this.label = label;
             this.count = count;
         }
@@ -124,5 +179,9 @@ public class FacetResult {
             return count;
         }
     }
+
+    public interface FacetResultRow {
+        String getValue(String columnName) throws Exception;
+    }
 }
 

Modified: 
jackrabbit/oak/branches/1.8/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/query/facet/package-info.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.8/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/query/facet/package-info.java?rev=1847231&r1=1847230&r2=1847231&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.8/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/query/facet/package-info.java
 (original)
+++ 
jackrabbit/oak/branches/1.8/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/query/facet/package-info.java
 Fri Nov 23 09:27:31 2018
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("1.0.1")
+@Version("1.1.0")
 package org.apache.jackrabbit.oak.query.facet;
 
 import org.osgi.annotation.versioning.Version;

Modified: 
jackrabbit/oak/branches/1.8/oak-query-spi/src/test/java/org/apache/jackrabbit/oak/query/facet/FacetResultTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.8/oak-query-spi/src/test/java/org/apache/jackrabbit/oak/query/facet/FacetResultTest.java?rev=1847231&r1=1847230&r2=1847231&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.8/oak-query-spi/src/test/java/org/apache/jackrabbit/oak/query/facet/FacetResultTest.java
 (original)
+++ 
jackrabbit/oak/branches/1.8/oak-query-spi/src/test/java/org/apache/jackrabbit/oak/query/facet/FacetResultTest.java
 Fri Nov 23 09:27:31 2018
@@ -16,16 +16,21 @@
  */
 package org.apache.jackrabbit.oak.query.facet;
 
+import com.google.common.collect.Maps;
+import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
+import org.apache.jackrabbit.oak.query.facet.FacetResult.Facet;
+import org.apache.jackrabbit.oak.query.facet.FacetResult.FacetResultRow;
+import org.junit.Test;
+
 import javax.jcr.Value;
 import javax.jcr.query.QueryResult;
 import javax.jcr.query.Row;
 import javax.jcr.query.RowIterator;
+import java.util.List;
+import java.util.Map;
 
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.apache.jackrabbit.oak.spi.query.QueryConstants.REP_FACET;
+import static org.junit.Assert.*;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -65,4 +70,142 @@ public class FacetResultTest {
         assertEquals(1, facetResult.getFacets("jcr:title").get(1).getCount(), 
0);
     }
 
+    @Test
+    public void simpleMergeFacets() {
+        String r1c1Facet = json(f("l1", 2), f("l2", 1));
+        String r2c1Facet = json(f("l2", 4), f("l1", 1));
+
+        FacetResult merged = facet(new FacetColumn("x", r1c1Facet, r2c1Facet));
+
+        FacetResult expected = facet(new FacetColumn("x", json(f("l2", 5), 
f("l1", 3))));
+
+        verify(expected, merged);
+    }
+
+    @Test
+    public void uniqueLabelsMergeFacets() {
+        String r1c1Facet = json(f("l1", 1));
+        String r2c1Facet = json(f("l2", 2));
+
+        FacetResult merged = facet(new FacetColumn("x", r1c1Facet, r2c1Facet));
+
+        FacetResult expected = facet(new FacetColumn("x", json(f("l2", 2), 
f("l1", 1))));
+
+        verify(expected, merged);
+    }
+
+    @Test
+    public void multipleColumns() {
+        String r1c1Facet = json(f("l1", 1));
+        String r2c1Facet = json(f("l2", 2));
+
+        String r1c2Facet = json(f("m1", 2));
+        String r2c2Facet = json(f("m2", 1));
+
+        FacetResult merged = facet(
+                new FacetColumn("x", r1c1Facet, r2c1Facet),
+                new FacetColumn("y", r1c2Facet, r2c2Facet)
+        );
+
+        FacetResult expected = facet(
+                new FacetColumn("x", json(f("l2", 2), f("l1", 1))),
+                new FacetColumn("y", json(f("m1", 2), f("m2", 1)))
+        );
+
+        verify(expected, merged);
+    }
+
+    @Test
+    public void multipleColumnsWithNullColumns() {
+        String r2c1Facet = json(f("l1", 1));
+        String r1c2Facet = json(f("m1", 1));
+
+        FacetResult merged = facet(
+                new FacetColumn("x", null, r2c1Facet),
+                new FacetColumn("y", r1c2Facet, null)
+        );
+
+        FacetResult expected = facet(
+                new FacetColumn("x", json(f("l1", 1))),
+                new FacetColumn("y", json(f("m1", 1)))
+        );
+
+        verify(expected, merged);
+    }
+
+    private FacetResult facet(FacetColumn ... facetColumns) {
+        String[] colNames = new String[facetColumns.length];
+        colNames[0] = facetColumns[0].colName;
+
+        int numRows = facetColumns[0].facets.length;
+
+        for (int i = 1; i < facetColumns.length; i++) {
+            assertEquals("numRows for col num " + i + " wasn't same as first", 
numRows, facetColumns[i].facets.length);
+
+            colNames[i] = facetColumns[i].colName;
+        }
+
+        FacetResultRow[] facetResultRows = new FacetResultRow[numRows];
+
+        for (int i = 0; i < numRows; i++) {
+            Map<String, String> columns = Maps.newHashMap();
+
+            for (FacetColumn col : facetColumns) {
+                columns.put(col.colName, col.facets[i]);
+            }
+
+            facetResultRows[i] = new FacetResultRow() {
+                final Map<String, String> cols = columns;
+                @Override
+                public String getValue(String columnName) {
+                    return cols.get(columnName);
+                }
+            };
+        }
+
+        return new FacetResult(colNames, facetResultRows);
+    }
+
+    private static String json(Facet ... facets) {
+        JsopBuilder builder = new JsopBuilder();
+        builder.object();
+        for (Facet facet : facets) {
+            builder.key(facet.getLabel());
+            builder.value(facet.getCount());
+        }
+        builder.endObject();
+
+        return builder.toString();
+    }
+
+    private static class FacetColumn {
+        final String colName;
+        final String[] facets;
+
+        FacetColumn(String colName, String ... facets) {
+            this.colName = REP_FACET + "(" + colName + ")";
+            this.facets = facets;
+        }
+    }
+
+    private static Facet f(String label, int count) {
+        return new Facet(label, count);
+    }
+
+    private static void verify(FacetResult expected, FacetResult result) {
+        assertEquals("Dimension mismatch", expected.getDimensions(), 
result.getDimensions());
+
+        for (String dim : expected.getDimensions()) {
+            List<Facet> expectedFacets = expected.getFacets(dim);
+            List<Facet> resultFacets = result.getFacets(dim);
+
+            for (int i = 0; i < expectedFacets.size(); i++) {
+                Facet expectedFacet = expectedFacets.get(i);
+                Facet resultFacet = resultFacets.get(i);
+
+                assertEquals("label mismatch for dim " + dim, 
expectedFacet.getLabel(), resultFacet.getLabel());
+                assertEquals("count mismatch for dim " + dim, 
expectedFacet.getCount(), resultFacet.getCount());
+            }
+        }
+    }
 }
\ No newline at end of file


Reply via email to