Merge pull request #13 from apigee/DPS-1108

Fixes bug with too small page size when only intersecting 2 terms
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/cassandra/QueryProcessor.java b/stack/core/src/main/java/org/apache/usergrid/persistence/cassandra/QueryProcessor.java
index 459926e..9ccf240 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/cassandra/QueryProcessor.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/cassandra/QueryProcessor.java
@@ -96,7 +96,7 @@
 
     private int size;
     private Query query;
-    private int pageSizeHint;
+    private int sliceCount;
 
 
     public QueryProcessor( Query query, CollectionInfo collectionInfo, EntityManager em,
@@ -131,7 +131,8 @@
 
     private void process() throws PersistenceException {
 
-        int opCount = 0;
+
+        sliceCount = 0;
 
         // no operand. Check for sorts
         if ( rootOperand != null ) {
@@ -143,16 +144,16 @@
 
             rootNode = visitor.getRootNode();
 
-            opCount = visitor.getSliceCount();
+            sliceCount = rootNode.getCount();
         }
 
         // see if we have sorts, if so, we can add them all as a single node at
         // the root
         if ( sorts.size() > 0 ) {
 
-            OrderByNode order = generateSorts( opCount );
+            OrderByNode order = generateSorts( sliceCount );
 
-            opCount += order.getFirstPredicate().getAllSlices().size();
+            sliceCount += order.getCount();
 
             rootNode = order;
         }
@@ -204,13 +205,6 @@
                 rootNode = allNode;
             }
         }
-
-        if ( opCount > 1 ) {
-            pageSizeHint = PAGE_SIZE;
-        }
-        else {
-            pageSizeHint = Math.min( size, PAGE_SIZE );
-        }
     }
 
 
@@ -320,7 +314,7 @@
 
         // stack for nodes that will be used to construct the tree and create
         // objects
-        private CountingStack<QueryNode> nodes = new CountingStack<QueryNode>();
+        private Stack<QueryNode> nodes = new Stack<QueryNode>();
 
 
         private int contextCount = -1;
@@ -622,43 +616,9 @@
             }
         }
 
-
-        public int getSliceCount() {
-            return nodes.getSliceCount();
-        }
     }
 
 
-    private static class CountingStack<T> extends Stack<T> {
-
-        private int count = 0;
-
-        /**
-         *
-         */
-        private static final long serialVersionUID = 1L;
-
-
-        @Override
-        public T push( final T entry ) {
-            if ( entry instanceof SliceNode ) {
-                //by definition a slicenode has a size.  Some have nothing to indicate full range scans, but they still count
-                //as an operand
-                count += Math.max(( ( SliceNode ) entry ).getAllSlices().size(), 1);
-            }
-            //not node creates a subraction, this needs to increase the count
-            else if ( entry instanceof NotNode ) {
-                count ++;
-            }
-
-            return super.push( entry );    //To change body of overridden methods use File | Settings | File Templates.
-        }
-
-
-        public int getSliceCount() {
-            return count;
-        }
-    }
 
 
     /**
@@ -670,11 +630,14 @@
          * It is crucial that the root iterator only needs the result set size per page
          * otherwise our cursor logic will fail when passing cursor data to the leaf nodes
          *******/
-        if ( node == rootNode ) {
+
+        //if it's a root node, and there's only 1 slice to check in the entire tree, then just select what we need
+        //so we short circuit on range scans faster.  otherwise it's more efficient to make less trips with candidates we discard from cassandra
+        if ( node == rootNode && sliceCount == 1 ) {
             return size;
         }
 
-        return pageSizeHint;
+        return PAGE_SIZE;
     }
 
 
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/AllNode.java b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/AllNode.java
index ffc7ef2..d870e49 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/AllNode.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/AllNode.java
@@ -52,6 +52,12 @@
 
 
     @Override
+    public int getCount() {
+        return 1;
+    }
+
+
+    @Override
     public String toString() {
         return "AllNode";
     }
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/BooleanNode.java b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/BooleanNode.java
index a6be2e8..ac3e42d 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/BooleanNode.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/BooleanNode.java
@@ -43,6 +43,12 @@
 
 
     @Override
+    public int getCount() {
+       return left.getCount()+ right.getCount();
+    }
+
+
+    @Override
     public String toString() {
         return "BooleanNode [left=" + left + ", right=" + right + "]";
     }
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/EmailIdentifierNode.java b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/EmailIdentifierNode.java
index 9266518..92dffee 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/EmailIdentifierNode.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/EmailIdentifierNode.java
@@ -41,6 +41,12 @@
     }
 
 
+    @Override
+    public int getCount() {
+        return 1;
+    }
+
+
     public Identifier getIdentifier() {
         return identifier;
     }
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/NameIdentifierNode.java b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/NameIdentifierNode.java
index 626be8c..2bd1b05 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/NameIdentifierNode.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/NameIdentifierNode.java
@@ -38,6 +38,12 @@
     }
 
 
+    @Override
+    public int getCount() {
+        return 1;
+    }
+
+
     public String getName() {
         return name;
     }
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/NotNode.java b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/NotNode.java
index aee8886..06b8208 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/NotNode.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/NotNode.java
@@ -57,6 +57,12 @@
 
 
     @Override
+    public int getCount() {
+        return subtractNode.getCount() + keepNode.getCount();
+    }
+
+
+    @Override
     public String toString() {
         return "NotNode [child=" + subtractNode + "]";
     }
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/OrderByNode.java b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/OrderByNode.java
index 655131e..ae3eb9e 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/OrderByNode.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/OrderByNode.java
@@ -83,9 +83,15 @@
     }
 
 
+    @Override
+    public int getCount() {
+        return firstPredicate.getCount() + secondarySorts.size();
+    }
+
+
     /* (non-Javadoc)
-     * @see java.lang.Object#toString()
-     */
+         * @see java.lang.Object#toString()
+         */
     @Override
     public String toString() {
         return "OrderByNode [sorts=" + secondarySorts + "]";
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/QueryNode.java b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/QueryNode.java
index 478bef9..5d3f0aa 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/QueryNode.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/QueryNode.java
@@ -26,4 +26,10 @@
 
     /** Visit this node */
     public abstract void visit( NodeVisitor visitor ) throws Exception;
+
+
+    /**
+     * Get the count of the total number of slices in our tree from this node and it's children
+     */
+    public abstract int getCount();
 }
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/SliceNode.java b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/SliceNode.java
index da6d234..046b517 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/SliceNode.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/SliceNode.java
@@ -59,6 +59,12 @@
     }
 
 
+    @Override
+    public int getCount() {
+        return pairs.size();
+    }
+
+
     /**
      * Set the start value. If the range pair doesn't exist, it's created
      *
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/UuidIdentifierNode.java b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/UuidIdentifierNode.java
index 56cc268..42e2c08 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/UuidIdentifierNode.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/UuidIdentifierNode.java
@@ -42,6 +42,12 @@
     }
 
 
+    @Override
+    public int getCount() {
+        return 1;
+    }
+
+
     public UUID getUuid() {
         return uuid;
     }
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/WithinNode.java b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/WithinNode.java
index d207f82..6551aee 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/WithinNode.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/query/ir/WithinNode.java
@@ -90,6 +90,12 @@
 
 
     @Override
+    public int getCount() {
+        return 1;
+    }
+
+
+    @Override
     public String toString() {
         return "WithinNode [propertyName=" + propertyName + ", distance=" + distance + ", lattitude=" + lattitude
                 + ", longitude=" + longitude + "]";
diff --git a/stack/core/src/test/java/org/apache/usergrid/persistence/cassandra/QueryProcessorTest.java b/stack/core/src/test/java/org/apache/usergrid/persistence/cassandra/QueryProcessorTest.java
index 683e1c0..c1fe7ee 100644
--- a/stack/core/src/test/java/org/apache/usergrid/persistence/cassandra/QueryProcessorTest.java
+++ b/stack/core/src/test/java/org/apache/usergrid/persistence/cassandra/QueryProcessorTest.java
@@ -24,12 +24,15 @@
 import org.antlr.runtime.ANTLRStringStream;
 import org.antlr.runtime.TokenRewriteStream;
 import org.junit.Test;
+
 import org.apache.usergrid.cassandra.Concurrent;
 import org.apache.usergrid.persistence.Query;
 import org.apache.usergrid.persistence.exceptions.PersistenceException;
 import org.apache.usergrid.persistence.query.ir.AndNode;
 import org.apache.usergrid.persistence.query.ir.NotNode;
 import org.apache.usergrid.persistence.query.ir.OrNode;
+import org.apache.usergrid.persistence.query.ir.OrderByNode;
+import org.apache.usergrid.persistence.query.ir.QueryNode;
 import org.apache.usergrid.persistence.query.ir.QuerySlice;
 import org.apache.usergrid.persistence.query.ir.SliceNode;
 import org.apache.usergrid.persistence.query.ir.WithinNode;
@@ -42,7 +45,9 @@
 import static org.junit.Assert.assertTrue;
 
 
-/** @author tnine */
+/**
+ * @author tnine
+ */
 @Concurrent()
 public class QueryProcessorTest {
 
@@ -386,7 +391,9 @@
     }
 
 
-    /** Tests that when properties are not siblings, they are properly assigned to a SliceNode */
+    /**
+     * Tests that when properties are not siblings, they are properly assigned to a SliceNode
+     */
     @Test
     public void nestedCompression() throws Exception {
         String queryString =
@@ -453,7 +460,9 @@
     }
 
 
-    /** Tests that when there are multiple or with and clauses, the tree is constructed correctly */
+    /**
+     * Tests that when there are multiple or with and clauses, the tree is constructed correctly
+     */
     @Test
     public void nestedOrCompression() throws Exception {
         String queryString =
@@ -530,7 +539,9 @@
     }
 
 
-    /** Tests that when NOT is not the root operand the tree has a different root */
+    /**
+     * Tests that when NOT is not the root operand the tree has a different root
+     */
     @Test
     public void andNot() throws Exception {
         String queryString = "select * where a > 1 and not b = 2";
@@ -575,7 +586,9 @@
     }
 
 
-    /** Tests that when NOT is the root operand, a full scan range is performed. */
+    /**
+     * Tests that when NOT is the root operand, a full scan range is performed.
+     */
     @Test
     public void notRootOperand() throws Exception {
         String queryString = "select * where not b = 2";
@@ -724,4 +737,86 @@
         assertEquals( value, slice.getFinish().getValue() );
         assertTrue( slice.getFinish().isInclusive() );
     }
+
+
+    @Test
+    public void validateHintSizeForOrder() throws Exception {
+        String queryString = "order by name desc";
+
+        ANTLRStringStream in = new ANTLRStringStream( queryString );
+        QueryFilterLexer lexer = new QueryFilterLexer( in );
+        TokenRewriteStream tokens = new TokenRewriteStream( lexer );
+        QueryFilterParser parser = new QueryFilterParser( tokens );
+
+        /**
+         * Test set limit
+         */
+
+        final int limit = 105;
+
+        Query query = parser.ql().query;
+        query.setLimit( limit );
+
+        QueryProcessor processor = new QueryProcessor( query, null, null, null );
+
+        OrderByNode node = ( OrderByNode ) processor.getFirstNode();
+
+        assertEquals( limit, processor.getPageSizeHint( node ) );
+    }
+
+
+    @Test
+    public void validateHintSizeForEquality() throws Exception {
+        String queryString = "select * where X = 'Foo'";
+
+        ANTLRStringStream in = new ANTLRStringStream( queryString );
+        QueryFilterLexer lexer = new QueryFilterLexer( in );
+        TokenRewriteStream tokens = new TokenRewriteStream( lexer );
+        QueryFilterParser parser = new QueryFilterParser( tokens );
+
+        /**
+         * Test set limit
+         */
+
+        final int limit = 105;
+
+        Query query = parser.ql().query;
+        query.setLimit( limit );
+
+        QueryProcessor processor = new QueryProcessor( query, null, null, null );
+
+        SliceNode node = ( SliceNode ) processor.getFirstNode();
+
+        assertEquals( limit, processor.getPageSizeHint( node ) );
+    }
+
+
+    @Test
+    public void validateHintSizeForComplexQueries() throws Exception {
+        //        String queryString = "select * where y = 'Foo' AND z = 'Bar'";
+
+        String queryString = "select * where y = 'Foo' AND z = 'Bar'";
+
+        ANTLRStringStream in = new ANTLRStringStream( queryString );
+        QueryFilterLexer lexer = new QueryFilterLexer( in );
+        TokenRewriteStream tokens = new TokenRewriteStream( lexer );
+        QueryFilterParser parser = new QueryFilterParser( tokens );
+
+        /**
+         * Test set limit
+         */
+
+        final int limit = 105;
+
+        Query query = parser.ql().query;
+        query.setLimit( limit );
+
+        QueryProcessor processor = new QueryProcessor( query, null, null, null );
+
+        QueryNode slice =  processor.getFirstNode();
+
+        assertEquals( 1000, processor.getPageSizeHint( slice ) );
+
+
+    }
 }