You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by dp...@apache.org on 2017/06/28 18:17:43 UTC

[20/20] lucene-solr:master: SOLR-10123: Upgraded the Analytics Component to version 2.0

SOLR-10123: Upgraded the Analytics Component to version 2.0


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/d5963beb
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/d5963beb
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/d5963beb

Branch: refs/heads/master
Commit: d5963bebc43bdb712f2f1e9a29f944370f079b5f
Parents: 85a27a2
Author: Dennis Gove <dp...@gmail.com>
Authored: Wed Jun 28 09:39:47 2017 -0400
Committer: Dennis Gove <dp...@gmail.com>
Committed: Wed Jun 28 14:13:37 2017 -0400

----------------------------------------------------------------------
 solr/CHANGES.txt                                |    8 +
 .../apache/solr/analytics/AnalyticsDriver.java  |   82 +
 .../solr/analytics/AnalyticsExpression.java     |   64 +
 .../analytics/AnalyticsGroupingManager.java     |  239 ++
 .../solr/analytics/AnalyticsRequestManager.java |  279 ++
 .../solr/analytics/AnalyticsRequestParser.java  |  549 +++
 .../solr/analytics/ExpressionFactory.java       |  821 +++++
 .../analytics/accumulator/BasicAccumulator.java |  173 -
 .../accumulator/FacetingAccumulator.java        |  730 ----
 .../analytics/accumulator/ValueAccumulator.java |   40 -
 .../facet/FacetValueAccumulator.java            |   35 -
 .../facet/FieldFacetAccumulator.java            |  153 -
 .../facet/QueryFacetAccumulator.java            |   72 -
 .../facet/RangeFacetAccumulator.java            |   49 -
 .../accumulator/facet/package-info.java         |   24 -
 .../analytics/accumulator/package-info.java     |   23 -
 .../analytics/expression/BaseExpression.java    |   88 -
 .../expression/DualDelegateExpression.java      |   99 -
 .../solr/analytics/expression/Expression.java   |   39 -
 .../analytics/expression/ExpressionFactory.java |  175 -
 .../expression/MultiDelegateExpression.java     |  131 -
 .../expression/SingleDelegateExpression.java    |   88 -
 .../solr/analytics/expression/package-info.java |   23 -
 .../analytics/facet/AbstractSolrQueryFacet.java |  104 +
 .../solr/analytics/facet/AnalyticsFacet.java    |  166 +
 .../apache/solr/analytics/facet/PivotFacet.java |  114 +
 .../apache/solr/analytics/facet/PivotNode.java  |  263 ++
 .../apache/solr/analytics/facet/QueryFacet.java |   64 +
 .../apache/solr/analytics/facet/RangeFacet.java |  119 +
 .../solr/analytics/facet/SortableFacet.java     |  178 +
 .../solr/analytics/facet/StreamingFacet.java    |   32 +
 .../apache/solr/analytics/facet/ValueFacet.java |   60 +
 .../facet/compare/ConstantComparator.java       |   30 +
 .../facet/compare/DelegatingComparator.java     |   62 +
 .../facet/compare/ExpressionComparator.java     |   46 +
 .../facet/compare/FacetResultsComparator.java   |   52 +
 .../facet/compare/FacetValueComparator.java     |   37 +
 .../analytics/facet/compare/package-info.java   |   23 +
 .../solr/analytics/facet/package-info.java      |   23 +
 .../function/ExpressionCalculator.java          |   71 +
 .../MergingReductionCollectionManager.java      |   46 +
 .../function/ReductionCollectionManager.java    |  320 ++
 .../analytics/function/ReductionFunction.java   |   37 +
 .../function/field/AnalyticsField.java          |   69 +
 .../analytics/function/field/BooleanField.java  |  111 +
 .../function/field/BooleanMultiField.java       |  101 +
 .../analytics/function/field/DateField.java     |  108 +
 .../function/field/DateMultiField.java          |   47 +
 .../function/field/DateMultiPointField.java     |   47 +
 .../analytics/function/field/DoubleField.java   |   97 +
 .../function/field/DoubleMultiField.java        |   85 +
 .../function/field/DoubleMultiPointField.java   |   81 +
 .../analytics/function/field/FloatField.java    |  108 +
 .../function/field/FloatMultiField.java         |   91 +
 .../function/field/FloatMultiPointField.java    |   87 +
 .../solr/analytics/function/field/IntField.java |  129 +
 .../analytics/function/field/IntMultiField.java |  100 +
 .../function/field/IntMultiPointField.java      |   96 +
 .../analytics/function/field/LongField.java     |  107 +
 .../function/field/LongMultiField.java          |   89 +
 .../function/field/LongMultiPointField.java     |   86 +
 .../analytics/function/field/StringField.java   |   85 +
 .../function/field/StringMultiField.java        |   66 +
 .../analytics/function/field/package-info.java  |   23 +
 .../function/mapping/AbsoluteValueFunction.java |   54 +
 .../analytics/function/mapping/AddFunction.java |   68 +
 .../function/mapping/BottomFunction.java        |  163 +
 .../function/mapping/CompareFunction.java       |  614 ++++
 .../function/mapping/ConcatFunction.java        |   78 +
 .../function/mapping/DateMathFunction.java      |  156 +
 .../function/mapping/DateParseFunction.java     |  210 ++
 .../function/mapping/DivideFunction.java        |   51 +
 .../function/mapping/FillMissingFunction.java   |  842 +++++
 .../function/mapping/FilterFunction.java        |  722 ++++
 .../analytics/function/mapping/IfFunction.java  |  892 +++++
 .../function/mapping/JoinFunction.java          |   57 +
 .../function/mapping/LambdaFunction.java        | 3220 ++++++++++++++++++
 .../analytics/function/mapping/LogFunction.java |   51 +
 .../function/mapping/LogicFunction.java         |   90 +
 .../function/mapping/MultFunction.java          |   68 +
 .../function/mapping/NegateFunction.java        |   58 +
 .../mapping/NumericConvertFunction.java         |  256 ++
 .../function/mapping/PowerFunction.java         |   51 +
 .../function/mapping/RemoveFunction.java        |  796 +++++
 .../function/mapping/ReplaceFunction.java       |  914 +++++
 .../function/mapping/StringCastFunction.java    |   42 +
 .../function/mapping/SubtractFunction.java      |   51 +
 .../analytics/function/mapping/TopFunction.java |  163 +
 .../function/mapping/package-info.java          |   23 +
 .../solr/analytics/function/package-info.java   |   23 +
 .../function/reduction/CountFunction.java       |   87 +
 .../function/reduction/DocCountFunction.java    |   87 +
 .../function/reduction/MaxFunction.java         |  298 ++
 .../function/reduction/MedianFunction.java      |  200 ++
 .../function/reduction/MinFunction.java         |  298 ++
 .../function/reduction/MissingFunction.java     |   76 +
 .../function/reduction/OrdinalFunction.java     |  386 +++
 .../function/reduction/PercentileFunction.java  |  337 ++
 .../function/reduction/SumFunction.java         |   92 +
 .../function/reduction/UniqueFunction.java      |  101 +
 .../function/reduction/data/CountCollector.java |  188 +
 .../function/reduction/data/MaxCollector.java   |  476 +++
 .../function/reduction/data/MinCollector.java   |  476 +++
 .../function/reduction/data/ReductionData.java  |   24 +
 .../reduction/data/ReductionDataCollector.java  |  183 +
 .../reduction/data/SortedListCollector.java     |  363 ++
 .../function/reduction/data/SumCollector.java   |  124 +
 .../reduction/data/UniqueCollector.java         |  241 ++
 .../function/reduction/data/package-info.java   |   24 +
 .../function/reduction/package-info.java        |   23 +
 .../org/apache/solr/analytics/package-info.java |   23 +
 .../request/AbstractFieldFacetRequest.java      |   42 -
 .../request/AnalyticsContentHandler.java        |  314 --
 .../analytics/request/AnalyticsRequest.java     |  114 -
 .../request/AnalyticsRequestFactory.java        |  308 --
 .../solr/analytics/request/AnalyticsStats.java  |  138 -
 .../analytics/request/ExpressionRequest.java    |   72 -
 .../solr/analytics/request/FacetRequest.java    |   26 -
 .../analytics/request/FieldFacetRequest.java    |  172 -
 .../analytics/request/QueryFacetRequest.java    |   74 -
 .../analytics/request/RangeFacetRequest.java    |  129 -
 .../solr/analytics/request/package-info.java    |   24 -
 .../AbstractDelegatingStatsCollector.java       |   74 -
 .../statistics/MedianStatsCollector.java        |   76 -
 .../statistics/MinMaxStatsCollector.java        |  114 -
 .../statistics/NumericStatsCollector.java       |   68 -
 .../statistics/PercentileStatsCollector.java    |   80 -
 .../analytics/statistics/StatsCollector.java    |   69 -
 .../StatsCollectorSupplierFactory.java          |  646 ----
 .../statistics/UniqueStatsCollector.java        |   53 -
 .../solr/analytics/statistics/package-info.java |   24 -
 .../stream/AnalyticsShardRequestManager.java    |  245 ++
 .../stream/AnalyticsShardResponseParser.java    |   89 +
 .../solr/analytics/stream/package-info.java     |   23 +
 .../reservation/BooleanArrayReservation.java    |   44 +
 .../reservation/BooleanCheckedReservation.java  |   42 +
 .../stream/reservation/BooleanReservation.java  |   42 +
 .../reservation/DoubleArrayReservation.java     |   44 +
 .../reservation/DoubleCheckedReservation.java   |   43 +
 .../stream/reservation/DoubleReservation.java   |   42 +
 .../reservation/FloatArrayReservation.java      |   44 +
 .../reservation/FloatCheckedReservation.java    |   43 +
 .../stream/reservation/FloatReservation.java    |   42 +
 .../stream/reservation/IntArrayReservation.java |   42 +
 .../reservation/IntCheckedReservation.java      |   43 +
 .../stream/reservation/IntReservation.java      |   42 +
 .../reservation/LongArrayReservation.java       |   45 +
 .../reservation/LongCheckedReservation.java     |   43 +
 .../stream/reservation/LongReservation.java     |   42 +
 .../ReductionCheckedDataReservation.java        |   35 +
 .../ReductionDataArrayReservation.java          |   36 +
 .../reservation/ReductionDataReservation.java   |   53 +
 .../reservation/StringArrayReservation.java     |   45 +
 .../reservation/StringCheckedReservation.java   |   44 +
 .../stream/reservation/StringReservation.java   |   43 +
 .../stream/reservation/package-info.java        |   24 +
 .../read/BooleanCheckedDataReader.java          |   33 +
 .../read/BooleanDataArrayReader.java            |   36 +
 .../reservation/read/BooleanDataReader.java     |   33 +
 .../read/DoubleCheckedDataReader.java           |   32 +
 .../reservation/read/DoubleDataArrayReader.java |   35 +
 .../reservation/read/DoubleDataReader.java      |   32 +
 .../read/FloatCheckedDataReader.java            |   33 +
 .../reservation/read/FloatDataArrayReader.java  |   36 +
 .../reservation/read/FloatDataReader.java       |   33 +
 .../reservation/read/IntCheckedDataReader.java  |   32 +
 .../reservation/read/IntDataArrayReader.java    |   34 +
 .../stream/reservation/read/IntDataReader.java  |   32 +
 .../reservation/read/LongCheckedDataReader.java |   32 +
 .../reservation/read/LongDataArrayReader.java   |   35 +
 .../stream/reservation/read/LongDataReader.java |   32 +
 .../read/ReductionCheckedDataReader.java        |   54 +
 .../read/ReductionDataArrayReader.java          |   54 +
 .../reservation/read/ReductionDataReader.java   |   40 +
 .../read/StringCheckedDataReader.java           |   32 +
 .../reservation/read/StringDataArrayReader.java |   35 +
 .../reservation/read/StringDataReader.java      |   34 +
 .../stream/reservation/read/package-info.java   |   24 +
 .../write/BooleanCheckedDataWriter.java         |   33 +
 .../write/BooleanDataArrayWriter.java           |   36 +
 .../reservation/write/BooleanDataWriter.java    |   33 +
 .../write/DoubleCheckedDataWriter.java          |   34 +
 .../write/DoubleDataArrayWriter.java            |   36 +
 .../reservation/write/DoubleDataWriter.java     |   33 +
 .../write/FloatCheckedDataWriter.java           |   35 +
 .../reservation/write/FloatDataArrayWriter.java |   37 +
 .../reservation/write/FloatDataWriter.java      |   34 +
 .../reservation/write/IntCheckedDataWriter.java |   34 +
 .../reservation/write/IntDataArrayWriter.java   |   35 +
 .../stream/reservation/write/IntDataWriter.java |   33 +
 .../write/LongCheckedDataWriter.java            |   34 +
 .../reservation/write/LongDataArrayWriter.java  |   36 +
 .../reservation/write/LongDataWriter.java       |   33 +
 .../write/ReductionCheckedDataWriter.java       |   60 +
 .../write/ReductionDataArrayWriter.java         |   53 +
 .../reservation/write/ReductionDataWriter.java  |   40 +
 .../write/StringCheckedDataWriter.java          |   34 +
 .../write/StringDataArrayWriter.java            |   36 +
 .../reservation/write/StringDataWriter.java     |   37 +
 .../stream/reservation/write/package-info.java  |   24 +
 .../solr/analytics/util/AnalyticsParams.java    |  114 -
 .../solr/analytics/util/AnalyticsParsers.java   |  171 -
 .../util/AnalyticsResponseHeadings.java         |   36 +
 .../analytics/util/FacetRangeGenerator.java     |  356 ++
 .../solr/analytics/util/MedianCalculator.java   |    4 +
 .../solr/analytics/util/OldAnalyticsParams.java |  177 +
 .../util/OldAnalyticsRequestConverter.java      |  177 +
 .../solr/analytics/util/OrdinalCalculator.java  |  173 +
 .../analytics/util/PercentileCalculator.java    |  176 -
 .../analytics/util/RangeEndpointCalculator.java |  354 --
 .../util/function/BooleanConsumer.java          |   59 +
 .../analytics/util/function/FloatConsumer.java  |   59 +
 .../analytics/util/function/FloatSupplier.java  |   41 +
 .../analytics/util/function/package-info.java   |   23 +
 .../solr/analytics/util/package-info.java       |    3 +-
 .../AbsoluteValueDoubleFunction.java            |   60 -
 .../util/valuesource/AddDoubleFunction.java     |   49 -
 .../util/valuesource/ConstDateSource.java       |  112 -
 .../util/valuesource/ConstDoubleSource.java     |  104 -
 .../util/valuesource/ConstStringSource.java     |   50 -
 .../util/valuesource/DateFieldSource.java       |  131 -
 .../util/valuesource/DateMathFunction.java      |   71 -
 .../util/valuesource/DivDoubleFunction.java     |   48 -
 .../util/valuesource/DualDoubleFunction.java    |   94 -
 .../util/valuesource/FilterFieldSource.java     |  154 -
 .../util/valuesource/LogDoubleFunction.java     |   43 -
 .../util/valuesource/MultiDateFunction.java     |  133 -
 .../util/valuesource/MultiDoubleFunction.java   |  119 -
 .../valuesource/MultiplyDoubleFunction.java     |   49 -
 .../util/valuesource/NegateDoubleFunction.java  |   55 -
 .../util/valuesource/PowDoubleFunction.java     |   48 -
 .../util/valuesource/ReverseStringFunction.java |   45 -
 .../util/valuesource/SingleDoubleFunction.java  |   79 -
 .../util/valuesource/SingleStringFunction.java  |  117 -
 .../util/valuesource/package-info.java          |   24 -
 .../solr/analytics/value/AnalyticsValue.java    |   55 +
 .../analytics/value/AnalyticsValueStream.java   |  133 +
 .../solr/analytics/value/BooleanValue.java      |   85 +
 .../analytics/value/BooleanValueStream.java     |   55 +
 .../solr/analytics/value/ComparableValue.java   |   32 +
 .../apache/solr/analytics/value/DateValue.java  |  102 +
 .../solr/analytics/value/DateValueStream.java   |   62 +
 .../solr/analytics/value/DoubleValue.java       |   86 +
 .../solr/analytics/value/DoubleValueStream.java |   54 +
 .../apache/solr/analytics/value/FloatValue.java |   97 +
 .../solr/analytics/value/FloatValueStream.java  |   60 +
 .../apache/solr/analytics/value/IntValue.java   |  121 +
 .../solr/analytics/value/IntValueStream.java    |   71 +
 .../apache/solr/analytics/value/LongValue.java  |   97 +
 .../solr/analytics/value/LongValueStream.java   |   60 +
 .../solr/analytics/value/StringValue.java       |   71 +
 .../solr/analytics/value/StringValueStream.java |   49 +
 .../value/constant/ConstantBooleanValue.java    |   91 +
 .../value/constant/ConstantDateValue.java       |  103 +
 .../value/constant/ConstantDoubleValue.java     |   90 +
 .../value/constant/ConstantFloatValue.java      |   99 +
 .../value/constant/ConstantIntValue.java        |  118 +
 .../value/constant/ConstantLongValue.java       |  100 +
 .../value/constant/ConstantStringValue.java     |   79 +
 .../analytics/value/constant/ConstantValue.java |  128 +
 .../analytics/value/constant/package-info.java  |   23 +
 .../solr/analytics/value/package-info.java      |   23 +
 .../apache/solr/handler/AnalyticsHandler.java   |  147 +
 .../handler/component/AnalyticsComponent.java   |  119 +-
 .../java/org/apache/solr/handler/package.html   |   28 +
 .../response/AnalyticsShardResponseWriter.java  |   91 +
 .../java/org/apache/solr/response/package.html  |   28 +
 .../analytics/requestFiles/expressions.txt      |   70 -
 .../analytics/requestFiles/fieldFacetExtras.txt |   66 -
 .../analytics/requestFiles/fieldFacets.txt      |  132 -
 .../analytics/requestFiles/functions.txt        |   62 -
 .../analytics/requestFiles/noFacets.txt         |   74 -
 .../analytics/requestFiles/queryFacets.txt      |   45 -
 .../analytics/requestFiles/rangeFacets.txt      |  170 -
 .../analytics/requestXMLFiles/expressions.xml   |  285 --
 .../requestXMLFiles/fieldFacetExtras.xml        |  101 -
 .../analytics/requestXMLFiles/fieldFacets.xml   |  496 ---
 .../analytics/requestXMLFiles/functions.xml     |  246 --
 .../analytics/requestXMLFiles/noFacets.xml      |  310 --
 .../analytics/requestXMLFiles/queryFacets.xml   |   94 -
 .../analytics/requestXMLFiles/rangeFacets.xml   |  319 --
 .../test-files/solr/analytics/expressions.txt   |   65 +
 .../test-files/solr/analytics/facetSorting.txt  |    4 +
 .../solr/analytics/fieldFacetExtras.txt         |   66 +
 .../test-files/solr/analytics/fieldFacets.txt   |  132 +
 .../src/test-files/solr/analytics/functions.txt |   57 +
 .../src/test-files/solr/analytics/noFacets.txt  |   74 +
 .../test-files/solr/analytics/queryFacets.txt   |   27 +
 .../test-files/solr/analytics/rangeFacets.txt   |  161 +
 .../solr/collection1/conf/schema-analytics.xml  |    7 +-
 .../collection1/conf/solrconfig-analytics.xml   |   42 +
 .../solr/collection1/conf/solrconfig-basic.xml  |   40 -
 .../configsets/cloud-analytics/conf/schema.xml  |   63 +
 .../cloud-analytics/conf/solrconfig.xml         |   59 +
 .../AbstractAnalyticsStatsCloudTest.java        |  187 +
 .../analytics/AbstractAnalyticsStatsTest.java   |   14 +-
 .../apache/solr/analytics/NoFacetCloudTest.java |  557 +++
 .../org/apache/solr/analytics/NoFacetTest.java  |   46 +-
 .../analytics/expression/ExpressionTest.java    |   70 +-
 .../solr/analytics/expression/FunctionTest.java |  221 ++
 .../facet/AbstractAnalyticsFacetCloudTest.java  |  284 ++
 .../facet/AbstractAnalyticsFacetTest.java       |   37 +-
 .../solr/analytics/facet/FacetSortingTest.java  |   53 +
 .../analytics/facet/FieldFacetCloudTest.java    | 1214 +++++++
 .../facet/FieldFacetExtrasCloudTest.java        |  253 ++
 .../analytics/facet/FieldFacetExtrasTest.java   |    6 +-
 .../solr/analytics/facet/FieldFacetTest.java    |   63 +-
 .../analytics/facet/QueryFacetCloudTest.java    |  159 +
 .../solr/analytics/facet/QueryFacetTest.java    |   10 +-
 .../analytics/facet/RangeFacetCloudTest.java    |  588 ++++
 .../solr/analytics/facet/RangeFacetTest.java    |   43 +-
 .../util/valuesource/FunctionTest.java          |  233 --
 .../solr/handler/component/ResponseBuilder.java |    3 +
 313 files changed, 30374 insertions(+), 9933 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 208512c..9578698 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -116,6 +116,10 @@ Upgrading from Solr 6.x
      curl http://host:8983/solr/mycollection/config -d '{"set-user-property": {"update.autoCreateFields":"false"}}'
   Please see SOLR-10574 for details.
 
+* SOLR-10123: The Analytics Component has been upgraded to support distributed collections, expressions over multivalued
+  fields, a new JSON request language, and more. DocValues are now required for any field used in the analytics expression
+  whereas previously docValues was not required. Please see SOLR-10123 for details.
+
 New Features
 ----------------------
 * SOLR-9857, SOLR-9858: Collect aggregated metrics from nodes and shard leaders in overseer. (ab)
@@ -175,6 +179,10 @@ New Features
 
 * SOLR-10272: Use _default config set if no collection.configName is specified with CREATE (Ishan Chattopadhyaya)
 
+* SOLR-10123: Upgraded the Analytics Component to version 2.0 which now supports distributed collections, expressions over 
+  multivalued fields, a new JSON request language, and more. DocValues are now required for any field used in the analytics 
+  expression  whereas previously docValues was not required. Please see SOLR-10123 for details. (Houston Putman)
+
 Bug Fixes
 ----------------------
 * SOLR-9262: Connection and read timeouts are being ignored by UpdateShardHandler after SOLR-4509.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsDriver.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsDriver.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsDriver.java
new file mode 100644
index 0000000..21b053f
--- /dev/null
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsDriver.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.analytics;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.search.DocIdSet;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.solr.analytics.AnalyticsRequestManager.StreamingInfo;
+import org.apache.solr.analytics.facet.AbstractSolrQueryFacet.FacetValueQueryExecuter;
+import org.apache.solr.analytics.facet.StreamingFacet;
+import org.apache.solr.analytics.function.ReductionCollectionManager;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.search.Filter;
+import org.apache.solr.search.SolrIndexSearcher;
+
+public class AnalyticsDriver {
+  
+  /**
+   * Drive the collection of reduction data. This includes overall data as well as faceted data.
+   * 
+   * @param manager of the request to drive
+   * @param searcher the results of the query
+   * @param filter that represents the overall query
+   * @param queryRequest used for the search request
+   * @throws IOException if an error occurs while reading from Solr
+   */
+  public static void drive(AnalyticsRequestManager manager, SolrIndexSearcher searcher, Filter filter, SolrQueryRequest queryRequest) throws IOException {
+    StreamingInfo streamingInfo = manager.getStreamingFacetInfo();
+    Iterable<StreamingFacet> streamingFacets = streamingInfo.streamingFacets;
+    ReductionCollectionManager collectionManager = streamingInfo.streamingCollectionManager;
+    
+    Iterable<FacetValueQueryExecuter> facetExecuters = manager.getFacetExecuters(filter, queryRequest);
+    
+    // Streaming phase (Overall results & Value/Pivot Facets)
+    // Loop through all documents and collect reduction data for streaming facets and overall results
+    if (collectionManager.needsCollection()) {
+      List<LeafReaderContext> contexts = searcher.getTopReaderContext().leaves();
+      for (int leafNum = 0; leafNum < contexts.size(); leafNum++) {
+        LeafReaderContext context = contexts.get(leafNum);
+        DocIdSet dis = filter.getDocIdSet(context, null); // solr docsets already exclude any deleted docs
+        if (dis == null) {
+          continue;
+        }
+        DocIdSetIterator disi = dis.iterator();
+        if (disi != null) {
+          collectionManager.doSetNextReader(context);
+          int doc = disi.nextDoc();
+          while( doc != DocIdSetIterator.NO_MORE_DOCS){
+            // Add a document to the statistics being generated
+            collectionManager.collect(doc);
+            streamingFacets.forEach( facet -> facet.addFacetValueCollectionTargets() );
+            collectionManager.apply();
+            doc = disi.nextDoc();
+          }
+        }
+      }
+    }
+    
+    // Executing phase (Query/Range Facets)
+    // Send additional Solr Queries to compute facet values
+    for (FacetValueQueryExecuter executer : facetExecuters) {
+      executer.execute(searcher);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsExpression.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsExpression.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsExpression.java
new file mode 100644
index 0000000..044e371
--- /dev/null
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsExpression.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.analytics;
+
+import org.apache.solr.analytics.function.ReductionCollectionManager;
+import org.apache.solr.analytics.function.ReductionCollectionManager.ReductionDataCollection;
+import org.apache.solr.analytics.value.AnalyticsValue;
+
+/**
+ * A wrapper for a top-level analytics expression.
+ * The expression must have a name and be single valued.
+ */
+public class AnalyticsExpression {
+  private final AnalyticsValue expression;
+  private final String name;
+  
+  public AnalyticsExpression(String name, AnalyticsValue expression) {
+    this.name = name;
+    this.expression = expression;
+  }
+  
+  public String getName() {
+    return name;
+  }
+  
+  public AnalyticsValue getExpression() {
+    return expression;
+  }
+  
+  /**
+   * Get the current value of the expression.
+   * This method can, and will, be called multiple times to return different values.
+   * The value returned is based on the {@link ReductionDataCollection} given
+   * to the {@link ReductionCollectionManager#setData} method.
+   * 
+   * @return the current value of the expression
+   */
+  public Object toObject() {
+    return expression.getObject();
+  }
+  
+  /**
+   * NOTE: Must be called after {@link #toObject()} is called, otherwise the value is not guaranteed to be correct.
+   * 
+   * @return whether the current value of the expression exists.
+   */
+  public boolean exists() {
+    return expression.exists();
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsGroupingManager.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsGroupingManager.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsGroupingManager.java
new file mode 100644
index 0000000..a95a451
--- /dev/null
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsGroupingManager.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file inputtributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * inputtributed under the License is inputtributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.analytics;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Consumer;
+
+import org.apache.solr.analytics.facet.AnalyticsFacet;
+import org.apache.solr.analytics.facet.PivotFacet;
+import org.apache.solr.analytics.facet.AbstractSolrQueryFacet;
+import org.apache.solr.analytics.facet.QueryFacet;
+import org.apache.solr.analytics.facet.RangeFacet;
+import org.apache.solr.analytics.facet.StreamingFacet;
+import org.apache.solr.analytics.facet.ValueFacet;
+import org.apache.solr.analytics.facet.AbstractSolrQueryFacet.FacetValueQueryExecuter;
+import org.apache.solr.analytics.function.ExpressionCalculator;
+import org.apache.solr.analytics.function.ReductionCollectionManager;
+import org.apache.solr.analytics.util.AnalyticsResponseHeadings;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.search.Filter;
+
+/**
+ * The manager for faceted analytics. This class manages one grouping of facets and expressions to compute
+ * over those facets.
+ * 
+ * <p>
+ * This class will only manage generating faceted results, not overall results.
+ */
+public class AnalyticsGroupingManager {
+  private final String name;
+  private final ReductionCollectionManager reductionCollectionManager;
+
+  private final Collection<AnalyticsExpression> topLevelExpressions;
+  private final ExpressionCalculator expressionCalculator;
+  
+  private final Map<String, AnalyticsFacet> facets;
+  
+  public AnalyticsGroupingManager(String name,
+                                  ReductionCollectionManager reductionCollectionManager,
+                                  Collection<AnalyticsExpression> topLevelExpressions) {
+    this.name = name;
+    this.reductionCollectionManager = reductionCollectionManager;
+
+    this.topLevelExpressions = topLevelExpressions;
+    this.expressionCalculator = new ExpressionCalculator(topLevelExpressions);
+
+    this.facets = new HashMap<>();
+  }
+
+  // This is outside of the method, since it is used in the lambda and cannot be a local non-final variable
+  private boolean hasStreamingFacets;
+  
+  /**
+   * Get the {@link StreamingFacet}s (e.g. {@link ValueFacet} and {@link PivotFacet}) contained within this grouping,
+   * returning them through the given consumer.
+   * 
+   * @param cons where the streaming facets are passed to
+   * @return whether the grouping contains streaming facets
+   */
+  public boolean getStreamingFacets(Consumer<StreamingFacet> cons) {
+    hasStreamingFacets = false;
+    facets.forEach( (name, facet) -> {
+      if (facet instanceof StreamingFacet) {
+        cons.accept((StreamingFacet)facet);
+        hasStreamingFacets = true;
+      }
+    });
+    return hasStreamingFacets;
+  }
+
+  /**
+   * Create the {@link FacetValueQueryExecuter}s for all {@link AbstractSolrQueryFacet}s
+   * (e.g. {@link QueryFacet} and {@link RangeFacet}) contained within this grouping.
+   * The executers are returned through the given consumer.
+   * 
+   * <p>
+   * One {@link FacetValueQueryExecuter} is created for each facet value to be returned for a facet.
+   * Since every {@link AbstractSolrQueryFacet} has discrete and user-defined facet values,
+   * unlike {@link StreamingFacet}s, a discrete number of {@link FacetValueQueryExecuter}s are created and returned.
+   * 
+   * @param filter representing the overall Solr Query of the request,
+   * will be combined with the facet value queries
+   * @param queryRequest from the overall search request
+   * @param cons where the executers are passed to
+   */
+  public void getFacetExecuters(Filter filter, SolrQueryRequest queryRequest, Consumer<FacetValueQueryExecuter> cons) {
+    facets.forEach( (name, facet) -> {
+      if (facet instanceof AbstractSolrQueryFacet) {
+        ((AbstractSolrQueryFacet)facet).createFacetValueExecuters(filter, queryRequest, cons);
+      }
+    });
+  }
+  
+  /**
+   * Add a facet to the grouping. All expressions in this grouping will be computed over the facet.
+   * 
+   * @param facet to compute expressions over
+   */
+  public void addFacet(AnalyticsFacet facet) {
+    facet.setExpressionCalculator(expressionCalculator);
+    facet.setReductionCollectionManager(reductionCollectionManager);
+    facets.put(facet.getName(), facet);
+  }
+  
+  /**
+   * Import the shard data for this grouping from a bit-stream,
+   * exported by the {@link #exportShardData} method in the each of the collection's shards.
+   * 
+   * @param input The bit-stream to import the grouping data from
+   * @throws IOException if an exception occurs while reading from the {@link DataInput}
+   */
+  public void importShardData(DataInput input) throws IOException {
+    // This allows mergeData() to import from the same input everytime it is called
+    // while the facets are importing.
+    reductionCollectionManager.setShardInput(input);
+    
+    int sz = input.readInt();
+    for (int i = 0; i < sz; ++i) {
+      facets.get(input.readUTF()).importShardData(input);
+    }
+  }
+  
+  /**
+   * Export the shard data for this grouping through a bit-stream,
+   * to be imported by the {@link #importShardData} method in the originating shard.
+   * 
+   * @param output The bit-stream to output the grouping data through
+   * @throws IOException if an exception occurs while writing to the {@link DataOutput}
+   */
+  public void exportShardData(DataOutput output) throws IOException {
+    // This allows exportData() to export to the same output everytime it is called
+    // while the facets are exporting.
+    reductionCollectionManager.setShardOutput(output);
+    
+    output.writeInt(facets.size());
+    for (Entry<String,AnalyticsFacet> facet : facets.entrySet()) {
+      output.writeUTF(facet.getKey());
+      facet.getValue().exportShardData(output);
+    }
+  }
+  
+  /**
+   * Get the {@link ReductionCollectionManager} that manages the collection of reduction data for the expressions
+   * contained within this grouping. 
+   * 
+   * @return the grouping's reduction manager
+   */
+  public ReductionCollectionManager getReductionManager() {
+    return reductionCollectionManager;
+  }
+
+  /**
+   * Create the response for this grouping, a mapping from each of it's facets' names to the facet's response.
+   * 
+   * @return the named list representation of the response
+   */
+  public Map<String,Object> createResponse() {
+    Map<String,Object> response = new HashMap<>();
+    
+    // Add the value facet buckets to the output
+    facets.forEach( (name, facet) -> response.put(name, facet.createResponse()) );
+
+    return response;
+  }
+  
+  /**
+   * Create the response for this grouping, but in the old style of response.
+   * This response has a bucket for the following if they are contained in the grouping:
+   * FieldFacets, RangeFacets and QueryFacets.
+   * Each facet's name and response are put into the bucket corresponding to its type.
+   * <p>
+   * Since groupings in the old notation must also return overall results, the overall results are
+   * passed in and the values are used to populate the grouping response.
+   * 
+   * @param overallResults of the expressions to add to the grouping response
+   * @return the named list representation of the response
+   */
+  public NamedList<Object> createOldResponse(Map<String,Object> overallResults) {
+    NamedList<Object> response = new NamedList<>();
+    
+    topLevelExpressions.forEach( expression -> response.add(expression.getName(), overallResults.get(name + expression.getName())));
+
+    NamedList<Object> fieldFacetResults = new NamedList<>();
+    NamedList<Object> rangeFacetResults = new NamedList<>();
+    NamedList<Object> queryFacetResults = new NamedList<>();
+    // Add the field facet buckets to the output
+    facets.forEach( (name, facet) -> {
+      // The old style of request only accepts field facets
+      // So we can assume that all value facets are field facets
+      if (facet instanceof ValueFacet) {
+        fieldFacetResults.add(name, facet.createOldResponse());
+      } else if (facet instanceof RangeFacet) {
+        rangeFacetResults.add(name, facet.createOldResponse());
+      } else if (facet instanceof QueryFacet) {
+        queryFacetResults.add(name, facet.createOldResponse());
+      }
+    });
+    if (fieldFacetResults.size() > 0) {
+      response.add(AnalyticsResponseHeadings.FIELD_FACETS, fieldFacetResults);
+    }
+    if (rangeFacetResults.size() > 0) {
+      response.add(AnalyticsResponseHeadings.RANGE_FACETS, rangeFacetResults);
+    }
+    if (queryFacetResults.size() > 0) {
+      response.add(AnalyticsResponseHeadings.QUERY_FACETS, queryFacetResults);
+    }
+    return response;
+  }
+
+  /**
+   * Get the name of the grouping.
+   * 
+   * @return the grouping name
+   */
+  public String getName() {
+    return name;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestManager.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestManager.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestManager.java
new file mode 100644
index 0000000..45b958f
--- /dev/null
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestManager.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.analytics;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.solr.analytics.facet.AbstractSolrQueryFacet;
+import org.apache.solr.analytics.facet.AbstractSolrQueryFacet.FacetValueQueryExecuter;
+import org.apache.solr.analytics.facet.StreamingFacet;
+import org.apache.solr.analytics.function.ExpressionCalculator;
+import org.apache.solr.analytics.function.ReductionCollectionManager;
+import org.apache.solr.analytics.function.ReductionCollectionManager.ReductionDataCollection;
+import org.apache.solr.analytics.stream.AnalyticsShardRequestManager;
+import org.apache.solr.analytics.util.AnalyticsResponseHeadings;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.search.Filter;
+
+/**
+ * The manager of an entire analytics request.
+ */
+public class AnalyticsRequestManager {
+  private final ReductionCollectionManager ungroupedReductionManager;
+  private ReductionDataCollection ungroupedData;
+  
+  private final Map<String, AnalyticsGroupingManager> groupingManagers;
+  
+  private final Collection<AnalyticsExpression> ungroupedExpressions;
+  private final ExpressionCalculator ungroupedExpressionCalculator;
+  
+  /**
+   * If the request is distributed, the manager for shard requests.
+   */
+  public String analyticsRequest;
+  public AnalyticsShardRequestManager shardStream;
+  public boolean sendShards; 
+  
+  /**
+   * Create an manager with the given ungrouped expressions. This is straightforward in the new
+   * style of request, however in the old olap-style requests all groupings' expressions are expected
+   * to be ungrouped as well.
+   * 
+   * 
+   * @param ungroupedReductionManager to manage the reduction collection for all ungrouped expressions
+   * @param ungroupedExpressions to compute overall results for
+   */
+  public AnalyticsRequestManager(ReductionCollectionManager ungroupedReductionManager,
+                                 Collection<AnalyticsExpression> ungroupedExpressions) {
+    this.ungroupedReductionManager = ungroupedReductionManager;
+    this.ungroupedData = ungroupedReductionManager.newDataCollection();
+    this.ungroupedReductionManager.addLastingCollectTarget(ungroupedData);
+    
+    this.ungroupedExpressions = ungroupedExpressions;
+    this.ungroupedExpressionCalculator = new ExpressionCalculator(ungroupedExpressions);
+    this.groupingManagers = new HashMap<>();
+  }
+  
+  /**
+   * Get the collection manager for ungrouped expressions, including grouped expressions if
+   * the old request notation is used.
+   * 
+   * @return the collection manager for the ungrouped expressions
+   */
+  public ReductionCollectionManager getUngroupedCollectionManager() {
+    return ungroupedReductionManager;
+  }
+  
+  /**
+   * Get the collection manager for all ungrouped expressions, including grouped expressions if
+   * the old request notation is used.
+   * 
+   * @return the collection manager for the ungrouped expressions
+   */
+  public ReductionDataCollection getUngroupedData() {
+    return ungroupedData;
+  }
+  
+  /**
+   * Return all ungrouped expressions, including grouped expressions if
+   * the old request notation is used.
+   * 
+   * @return an {@link Iterable} of the ungrouped expressions
+   */
+  public Iterable<AnalyticsExpression> getUngroupedExpressions() {
+    return ungroupedExpressions;
+  }
+  
+  /**
+   * Generate the results of all ungrouped expressions, including grouped expressions if
+   * the old request notation is used.
+   * 
+   * @param response the response to add the ungrouped results to.
+   */
+  public void addUngroupedResults(Map<String,Object> response) {
+    ungroupedReductionManager.setData(ungroupedData);
+    ungroupedExpressionCalculator.addResults(response);
+  }
+  
+  /**
+   * Generate the results of all ungrouped expressions, including grouped expressions if
+   * the old request notation is used.
+   * 
+   * @return the map containing the ungrouped results
+   */
+  public Map<String,Object> getUngroupedResults() {
+    ungroupedReductionManager.setData(ungroupedData);
+    return ungroupedExpressionCalculator.getResults();
+  }
+  
+  /**
+   * Add a grouping to the request.
+   * 
+   * @param groupingManager that manages the grouping
+   */
+  public void addGrouping(AnalyticsGroupingManager groupingManager) {
+    groupingManagers.put(groupingManager.getName(), groupingManager);
+  }
+  
+  /**
+   * Import the shard data for this request from a bit-stream,
+   * exported by the {@link #exportShardData} method in the each of the collection's shards.
+   * <p>
+   * First the overall data is imported, then the grouping data is imported.
+   * 
+   * @param input The bit-stream to import the shard data from
+   * @throws IOException if an exception occurs while reading from the {@link DataInput}
+   */
+  public synchronized void importShardData(DataInput input) throws IOException {
+    ungroupedReductionManager.setShardInput(input);
+    
+    // The ungroupedData will not exist for the first shard imported
+    if (ungroupedData == null) {
+      ungroupedData = ungroupedReductionManager.newDataCollectionIO();
+    } else {
+      ungroupedReductionManager.prepareReductionDataIO(ungroupedData);
+    }
+    ungroupedReductionManager.mergeData();
+    
+    int size = input.readInt();
+    while (--size >= 0) {
+      String groupingName = input.readUTF();
+      groupingManagers.get(groupingName).importShardData(input);
+    }
+  }
+  
+  /**
+   * Export the shard data for this request through a bit-stream,
+   * to be imported by the {@link #importShardData} method in the originating shard.
+   * <p>
+   * First the overall data is exported, then the grouping data is exported.
+   * 
+   * @param output The bit-stream to output the shard data through
+   * @throws IOException if an exception occurs while writing to the {@link DataOutput}
+   */
+  public void exportShardData(DataOutput output) throws IOException {
+    ungroupedReductionManager.setShardOutput(output);
+    
+    ungroupedReductionManager.prepareReductionDataIO(ungroupedData);
+    ungroupedReductionManager.exportData();
+    
+    output.writeInt(groupingManagers.size());
+    for (String groupingName : groupingManagers.keySet()) {
+      output.writeUTF(groupingName);
+      groupingManagers.get(groupingName).exportShardData(output);
+    }
+  }
+
+  /**
+   * Consolidate the information of all {@link StreamingFacet}s contained within the request, since
+   * they need to be collected along with the overall results during the streaming phase of the 
+   * {@link AnalyticsDriver}.
+   * 
+   * @return the info for all {@link StreamingFacet}s
+   */
+  public StreamingInfo getStreamingFacetInfo() {
+    StreamingInfo streamingInfo = new StreamingInfo();
+    ArrayList<ReductionCollectionManager> groupingCollectors = new ArrayList<>();
+    groupingManagers.values().forEach( grouping -> {
+      // If a grouping has streaming facets, then that groupings expressions
+      // must be collected during the streaming phase. 
+      if (grouping.getStreamingFacets( facet -> streamingInfo.streamingFacets.add(facet) )) {
+        groupingCollectors.add(grouping.getReductionManager());
+      }
+    });
+    
+    // Create an streaming collection manager to manage the collection of all ungrouped expressions and
+    // grouped expressions that are calculated over streaming facets.
+    streamingInfo.streamingCollectionManager = ungroupedReductionManager.merge(groupingCollectors);
+    return streamingInfo;
+  }
+  
+  /**
+   * Class to encapsulate all necessary data for collecting {@link StreamingFacet}s.
+   */
+  public static class StreamingInfo {
+    Collection<StreamingFacet> streamingFacets = new ArrayList<>();
+    /**
+     * Manages the collection of all expressions needed for streaming facets
+     */
+    ReductionCollectionManager streamingCollectionManager;
+  }
+
+  /**
+   * Create the {@link FacetValueQueryExecuter}s for all {@link AbstractSolrQueryFacet}s contained in the request.
+   * 
+   * @param filter representing the overall search query
+   * @param queryRequest of the overall search query
+   * @return an {@link Iterable} of executers
+   */
+  public Iterable<FacetValueQueryExecuter> getFacetExecuters(Filter filter, SolrQueryRequest queryRequest) {
+    ArrayList<FacetValueQueryExecuter> facetExecutors = new ArrayList<>();
+    groupingManagers.values().forEach( grouping -> {
+      grouping.getFacetExecuters(filter, queryRequest, executor -> facetExecutors.add(executor));
+    });
+    return facetExecutors;
+  }
+  
+  /**
+   * Create the response for a request given in the old olap-style format.
+   * The old response returned overall expressions within groupings.
+   * 
+   * @return a {@link NamedList} representation of the response
+   */
+  public NamedList<Object> createOldResponse() {
+    NamedList<Object> analyticsResponse = new NamedList<>();
+    Map<String,Object> ungroupedResults = getUngroupedResults();
+    groupingManagers.forEach( (name, groupingManager) -> {
+      analyticsResponse.add(name, groupingManager.createOldResponse(ungroupedResults));
+    });
+    
+    return analyticsResponse;
+  }
+  
+  /**
+   * Create the response for a request.
+   * 
+   * <p>
+   * NOTE: Analytics requests specified in the old olap-style format
+   * have their responses generated by {@link #createOldResponse()}.
+   * 
+   * @return a {@link Map} representation of the response
+   */
+  public Map<String,Object> createResponse() {
+    Map<String,Object> analyticsResponse = new HashMap<>();
+    if (ungroupedExpressions.size() > 0) {
+      addUngroupedResults(analyticsResponse);
+    }
+
+    Map<String,Object> groupingsResponse = new HashMap<>();
+    groupingManagers.forEach( (name, groupingManager) -> {
+      groupingsResponse.put(name, groupingManager.createResponse());
+    });
+    
+    if (groupingsResponse.size() > 0) {
+      analyticsResponse.put(AnalyticsResponseHeadings.GROUPINGS, groupingsResponse);
+    }
+    return analyticsResponse;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestParser.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestParser.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestParser.java
new file mode 100644
index 0000000..bfa62e2
--- /dev/null
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestParser.java
@@ -0,0 +1,549 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.analytics;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+import org.apache.solr.analytics.facet.PivotFacet;
+import org.apache.solr.analytics.facet.PivotNode;
+import org.apache.solr.analytics.facet.QueryFacet;
+import org.apache.solr.analytics.facet.RangeFacet;
+import org.apache.solr.analytics.facet.ValueFacet;
+import org.apache.solr.analytics.facet.SortableFacet.FacetSortSpecification;
+import org.apache.solr.analytics.facet.compare.DelegatingComparator;
+import org.apache.solr.analytics.facet.compare.FacetValueComparator;
+import org.apache.solr.analytics.facet.compare.FacetResultsComparator;
+import org.apache.solr.analytics.value.AnalyticsValue;
+import org.apache.solr.analytics.value.AnalyticsValueStream;
+import org.apache.solr.analytics.value.ComparableValue;
+import org.apache.solr.analytics.value.StringValueStream;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.params.FacetParams.FacetRangeInclude;
+import org.apache.solr.common.params.FacetParams.FacetRangeOther;
+import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.SchemaField;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Class to manage the parsing of new-style analytics requests.
+ */
+public class AnalyticsRequestParser {
+  
+  private static ObjectMapper mapper = new ObjectMapper();
+  
+  public static void init() {
+    mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+    mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true);
+  }
+  
+  public static final String analyticsParamName = "analytics";
+
+  private static Predicate<String> request         = acceptNames("request", "req");
+  private static Predicate<String> functions       = acceptNames("functions", "funcs", "f");
+  private static Predicate<String> expressions     = acceptNames("expressions", "exprs", "e");
+
+  private static Predicate<String> grouping        = acceptNames("grouping", "group", "g");
+  
+  private static Predicate<String> valueFacet      = acceptNames("valuefacet", "vf");
+  
+  private static Predicate<String> pivotFacet      = acceptNames("pivotfacet", "pf");
+  private static Predicate<String> pivot           = acceptNames("pivot", "p");
+  
+  private static Predicate<String> sort            = acceptNames("sort", "s");
+  private static Predicate<String> sortExpression  = acceptNames("expression", "expr");
+  private static Predicate<String> sortFacetValue  = acceptNames("facetvalue", "fv");
+  private static Predicate<String> sortDirection   = acceptNames("direction", "dir");
+  private static Predicate<String> sortAscending   = acceptNames("ascending", "asc", "a");
+  private static Predicate<String> sortDescending  = acceptNames("descending", "desc", "d");
+  private static Predicate<String> sortLimit       = acceptNames("limit", "l");
+  private static Predicate<String> sortOffset      = acceptNames("offset", "o");
+  
+  private static Predicate<String> rangeFacet      = acceptNames("rangefacet", "rf");
+  private static Predicate<String> rangeGaps       = acceptNames("gaps", "g");
+  private static Predicate<String> rangeHardEnd    = acceptNames("hardend", "he");
+  private static Predicate<String> rangeInclude    = acceptNames("include", "i");
+  private static Predicate<String> rangeOthers     = acceptNames("others", "o");
+  
+  private static Predicate<String> queryFacet      = acceptNames("queryfacet", "qf");
+  private static Predicate<String> query           = acceptNames("query", "q");
+  
+  private static Predicate<String> acceptNames(String... names) {
+    return Pattern.compile("^(?:" + Arrays.stream(names).reduce((a,b) -> a + "|" + b).orElse("") + ")$", Pattern.CASE_INSENSITIVE).asPredicate();
+  }
+  
+  // Defaults
+  public static final String DEFAULT_SORT_DIRECTION = "ascending";
+  public static final int DEFAULT_OFFSET = 0;
+  public static final int DEFAULT_LIMIT = -1;
+  public static final boolean DEFAULT_HARDEND = false;
+  
+  @JsonInclude(Include.NON_EMPTY)
+  public static class AnalyticsRequest {
+    public Map<String, String> functions;
+    public Map<String, String> expressions;
+    
+    public Map<String, AnalyticsGroupingRequest> groupings;
+  }
+  
+  public static class AnalyticsGroupingRequest {
+    public Map<String, String> expressions;
+    
+    public Map<String, AnalyticsFacetRequest> facets;
+  }
+  
+  @JsonTypeInfo(
+      use = JsonTypeInfo.Id.NAME,
+      include = JsonTypeInfo.As.PROPERTY,
+      property = "type"
+  )
+  @JsonSubTypes({
+    @Type(value = AnalyticsValueFacetRequest.class, name = "value"),
+    @Type(value = AnalyticsPivotFacetRequest.class, name = "pivot"),
+    @Type(value = AnalyticsRangeFacetRequest.class, name = "range"),
+    @Type(value = AnalyticsQueryFacetRequest.class, name = "query") }
+  )
+  @JsonInclude(Include.NON_EMPTY)
+  public static interface AnalyticsFacetRequest { }
+  
+  @JsonTypeName("value")
+  public static class AnalyticsValueFacetRequest implements AnalyticsFacetRequest {
+    public String expression;
+    public AnalyticsSortRequest sort;
+  }
+
+  @JsonTypeName("pivot")
+  public static class AnalyticsPivotFacetRequest implements AnalyticsFacetRequest {
+    public List<AnalyticsPivotRequest> pivots;
+  }
+  
+  public static class AnalyticsPivotRequest {
+    public String name;
+    public String expression;
+    public AnalyticsSortRequest sort;
+  }
+
+  @JsonInclude(Include.NON_EMPTY)
+  public static class AnalyticsSortRequest {
+    public List<AnalyticsSortCriteriaRequest> criteria;
+    public int limit = DEFAULT_LIMIT;
+    public int offset = DEFAULT_OFFSET;
+  }
+
+  @JsonTypeInfo(
+      use = JsonTypeInfo.Id.NAME,
+      include = JsonTypeInfo.As.PROPERTY,
+      property = "type"
+  )
+  @JsonSubTypes({
+    @Type(value = AnalyticsExpressionSortRequest.class, name = "expression"),
+    @Type(value = AnalyticsFacetValueSortRequest.class, name = "facetvalue") }
+  )
+  @JsonInclude(Include.NON_EMPTY)
+  public static abstract class AnalyticsSortCriteriaRequest {
+    public String direction;
+  }
+
+  @JsonTypeName("expression")
+  public static class AnalyticsExpressionSortRequest extends AnalyticsSortCriteriaRequest {
+    public String expression;
+  }
+
+  @JsonTypeName("facetvalue")
+  public static class AnalyticsFacetValueSortRequest extends AnalyticsSortCriteriaRequest { }
+
+  @JsonTypeName("range")
+  public static class AnalyticsRangeFacetRequest implements AnalyticsFacetRequest {
+    public String field;
+    public String start;
+    public String end;
+    public List<String> gaps;
+    public boolean hardend = DEFAULT_HARDEND;
+    public List<String> include;
+    public List<String> others;
+  }
+
+  @JsonTypeName("query")
+  public static class AnalyticsQueryFacetRequest implements AnalyticsFacetRequest {
+    public Map<String, String> queries;
+  }
+  
+  /* ***************
+   * Request & Groupings 
+   * ***************/
+  
+  public static AnalyticsRequestManager parse(AnalyticsRequest request, ExpressionFactory expressionFactory, boolean isDistribRequest) throws SolrException {
+    AnalyticsRequestManager manager = constructRequest(request, expressionFactory, isDistribRequest);
+    if (isDistribRequest) {
+      try {
+        manager.analyticsRequest = mapper.writeValueAsString(request);
+      } catch (JsonProcessingException e) {
+        throw new RuntimeException(e);
+      }
+    }
+    return manager;
+  }
+  
+  public static AnalyticsRequestManager parse(String rawRequest, ExpressionFactory expressionFactory, boolean isDistribRequest) throws SolrException {
+    JsonParser parser;
+    try {
+      parser = new JsonFactory().createParser(rawRequest)
+          .configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
+          .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+    } catch (IOException e1) {
+      throw new RuntimeException(e1);
+    }
+    AnalyticsRequest request;
+    try {
+      request = mapper.readValue(parser, AnalyticsRequest.class);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+
+    AnalyticsRequestManager manager = constructRequest(request, expressionFactory, isDistribRequest);
+    if (isDistribRequest) {
+      manager.analyticsRequest = rawRequest;
+    }
+    return manager;
+  }
+  
+  private static AnalyticsRequestManager constructRequest(AnalyticsRequest request, ExpressionFactory expressionFactory, boolean isDistribRequest) throws SolrException {
+    expressionFactory.startRequest();
+    
+    // Functions
+    if (request.functions != null) {
+      request.functions.forEach( (funcSig, retSig) -> expressionFactory.addUserDefinedVariableFunction(funcSig, retSig));
+    }
+    
+    // Expressions
+    Map<String,AnalyticsExpression> topLevelExpressions;
+    if (request.expressions != null) {
+      topLevelExpressions = constructExpressions(request.expressions, expressionFactory);
+    } else {
+      topLevelExpressions = new HashMap<>();
+    }
+    AnalyticsRequestManager manager = new AnalyticsRequestManager(expressionFactory.createReductionManager(isDistribRequest), topLevelExpressions.values());
+    
+    // Groupings
+    if (request.groupings != null) {
+      request.groupings.forEach( (name, grouping) -> {
+        manager.addGrouping(constructGrouping(name, grouping, expressionFactory, isDistribRequest));
+      });
+    }
+    return manager;
+  }
+  
+  private static AnalyticsGroupingManager constructGrouping(String name, AnalyticsGroupingRequest grouping, ExpressionFactory expressionFactory, boolean isDistribRequest) throws SolrException {
+    expressionFactory.startGrouping();
+    
+    // Expressions
+    if (grouping.expressions == null || grouping.expressions.size() == 0) {
+      throw new SolrException(ErrorCode.BAD_REQUEST,"Groupings must contain at least one expression, '" + name + "' has none.");
+    }
+    
+    Map<String,AnalyticsExpression> expressions = constructExpressions(grouping.expressions, expressionFactory);
+    AnalyticsGroupingManager manager = new AnalyticsGroupingManager(name, 
+                                                                    expressionFactory.createGroupingReductionManager(isDistribRequest), 
+                                                                    expressions.values());
+    
+    if (grouping.facets == null) {
+      throw new SolrException(ErrorCode.BAD_REQUEST,"Groupings must contain at least one facet, '" + name + "' has none.");
+    }
+    // Parse the facets
+    grouping.facets.forEach( (facetName, facet) -> {
+      if (facet instanceof AnalyticsValueFacetRequest) {
+        manager.addFacet(constructValueFacet(facetName, (AnalyticsValueFacetRequest) facet, expressionFactory, expressions));
+      } else if (facet instanceof AnalyticsPivotFacetRequest) {
+        manager.addFacet(constructPivotFacet(facetName, (AnalyticsPivotFacetRequest) facet, expressionFactory, expressions));
+      } else if (facet instanceof AnalyticsRangeFacetRequest) {
+        manager.addFacet(constructRangeFacet(facetName, (AnalyticsRangeFacetRequest) facet, expressionFactory.getSchema()));
+      } else if (facet instanceof AnalyticsQueryFacetRequest) {
+        manager.addFacet(constructQueryFacet(facetName, (AnalyticsQueryFacetRequest) facet));
+      } else {
+        throw new SolrException(ErrorCode.BAD_REQUEST,"The facet type, '" + facet.getClass().toString() + "' in "
+            + "grouping '" + name + "' is not a valid type of facet"); 
+      }
+    });
+    
+    return manager;
+  }
+  
+  /* ***************
+   * Expression & Functions 
+   * ***************/
+  
+  private static Map<String, AnalyticsExpression> constructExpressions(Map<String, String> rawExpressions, ExpressionFactory expressionFactory) throws SolrException {
+    Map<String, AnalyticsExpression> expressions = new HashMap<>();
+    rawExpressions.forEach( (name, expression) -> {
+      AnalyticsValueStream exprVal = expressionFactory.createExpression(expression);
+      if (exprVal instanceof AnalyticsValue) {
+        if (exprVal.getExpressionType().isReduced()) {
+          expressions.put(name, (new AnalyticsExpression(name, (AnalyticsValue)exprVal)));
+        } else {
+          throw new SolrException(ErrorCode.BAD_REQUEST,"Top-level expressions must be reduced, the '" + name + "' expression is not.");
+        }
+      } else {
+        throw new SolrException(ErrorCode.BAD_REQUEST,"Top-level expressions must be single-valued, the '" + name + "' expression is not.");
+      }
+    });
+    return expressions;
+  }
+  
+  /* ***************
+   * FACETS 
+   * ***************/
+  
+  /*
+   * Value Facets
+   */
+  
+  private static ValueFacet constructValueFacet(String name, AnalyticsValueFacetRequest facetRequest, ExpressionFactory expressionFactory, Map<String, AnalyticsExpression> expressions) throws SolrException {
+    if (facetRequest.expression == null) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Value Facets must contain a mapping expression to facet over, '" + name + "' has none.");
+    }
+    
+    // The second parameter must be a mapping expression
+    AnalyticsValueStream expr = expressionFactory.createExpression(facetRequest.expression);
+    if (!expr.getExpressionType().isUnreduced()) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Value Facet expressions must be mapping expressions, "
+          + "the following expression in value facet '" + name + "' contains a reduction: " + facetRequest.expression);
+    }
+    if (!(expr instanceof StringValueStream)) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Value Facet expressions must be castable to string expressions, "
+          + "the following expression in value facet '" + name + "' is not: " + facetRequest.expression);
+    }
+    
+    ValueFacet facet = new ValueFacet(name, (StringValueStream)expr);
+    
+    // Check if the value facet is sorted
+    if (facetRequest.sort != null) {
+      facet.setSort(constructSort(facetRequest.sort, expressions));
+    }
+    return facet;
+  }
+
+  /*
+   * Pivot Facets
+   */
+  
+  private static PivotFacet constructPivotFacet(String name, AnalyticsPivotFacetRequest facetRequest, ExpressionFactory expressionFactory, Map<String, AnalyticsExpression> expressions) throws SolrException {
+    PivotNode<?> topPivot = null;
+    
+    // Pivots
+    if (facetRequest.pivots == null || facetRequest.pivots.size() == 0) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Pivot Facets must contain at least one pivot to facet over, '" + name + "' has none.");
+    }
+    
+    ListIterator<AnalyticsPivotRequest> iter = facetRequest.pivots.listIterator(facetRequest.pivots.size());
+    while (iter.hasPrevious()) {
+      topPivot = constructPivot(iter.previous(), topPivot, expressionFactory, expressions);
+    }
+    
+    return new PivotFacet(name, topPivot);
+  }
+  
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  private static PivotNode<?> constructPivot(AnalyticsPivotRequest pivotRequest,
+                                      PivotNode<?> childPivot,
+                                      ExpressionFactory expressionFactory,
+                                      Map<String, AnalyticsExpression> expressions) throws SolrException {
+    if (pivotRequest.name == null || pivotRequest.name.length() == 0) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Pivots must have a name.");
+    }
+    if (pivotRequest.expression == null) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Pivots must have an expression to facet over, '" + pivotRequest.name + "' does not.");
+    }
+    
+    // The second parameter must be a mapping expression
+    AnalyticsValueStream expr = expressionFactory.createExpression(pivotRequest.expression);
+    if (!expr.getExpressionType().isUnreduced()) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Pivot expressions must be mapping expressions, "
+          + "the following expression in pivot '" + pivotRequest.name + "' contains a reduction: " + pivotRequest.expression);
+    }
+    if (!(expr instanceof StringValueStream)) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Pivot expressions must be castable to string expressions, "
+          + "the following expression in pivot '" + pivotRequest.name + "' is not: '" + pivotRequest.expression);
+    }
+    
+    PivotNode<?> pivot;
+    if (childPivot == null) {
+      pivot = new PivotNode.PivotLeaf(pivotRequest.name, (StringValueStream)expr);
+    } else {
+      pivot = new PivotNode.PivotBranch(pivotRequest.name, (StringValueStream)expr, childPivot);
+    }
+    
+    // Check if the pivot is sorted
+    if (pivotRequest.sort != null) {
+      pivot.setSort(constructSort(pivotRequest.sort, expressions));
+    }
+    return pivot;
+  }
+  
+  /*
+   * Range Facets
+   */
+
+  private static RangeFacet constructRangeFacet(String name, AnalyticsRangeFacetRequest facetRequest, IndexSchema schema) throws SolrException {
+    if (facetRequest.field == null || facetRequest.field.length() == 0) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Range Facets must specify a field to facet over, '" +name + "' does not.");
+    }
+    SchemaField field = schema.getFieldOrNull(facetRequest.field);
+    if (field == null) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Range Facets must have a valid field as the second parameter. The '" + name + "' facet "
+          + "tries to facet over the non-existent field: " + facetRequest.field);
+    }
+
+    if (facetRequest.start == null || facetRequest.start.length() == 0) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Range Facets must specify a start value, '" +name + "' does not.");
+    }
+    if (facetRequest.end == null || facetRequest.end.length() == 0) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Range Facets must specify a end value, '" +name + "' does not.");
+    }
+    if (facetRequest.gaps == null || facetRequest.gaps.size() == 0) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Range Facets must specify a gap or list of gaps to determine facet buckets, '" +name + "' does not.");
+    }
+    RangeFacet facet = new RangeFacet(name, field, facetRequest.start, facetRequest.end, facetRequest.gaps);
+
+    facet.setHardEnd(facetRequest.hardend);
+
+    if (facetRequest.include != null && facetRequest.include.size() > 0) {
+      facet.setInclude(constructInclude(facetRequest.include));
+    }
+    if (facetRequest.others != null && facetRequest.others.size() > 0) {
+      facet.setOthers(constructOthers(facetRequest.others, name));
+    }
+    return facet;
+  }
+
+  private static EnumSet<FacetRangeInclude> constructInclude(List<String> includes) throws SolrException {
+    return FacetRangeInclude.parseParam(includes.toArray(new String[includes.size()]));
+  }
+
+  private static EnumSet<FacetRangeOther> constructOthers(List<String> othersRequest, String facetName) throws SolrException {
+    EnumSet<FacetRangeOther> others = EnumSet.noneOf(FacetRangeOther.class);
+    for (String rawOther : othersRequest) {
+      if (!others.add(FacetRangeOther.get(rawOther))) {
+        throw new SolrException(ErrorCode.BAD_REQUEST, "Duplicate include value '" + rawOther + "' found in range facet '" + facetName + "'");
+      }
+    }
+    if (others.contains(FacetRangeOther.NONE)) {
+      if (others.size() > 1) {
+        throw new SolrException(ErrorCode.BAD_REQUEST, "Include value 'NONE' is used with other includes in a range facet '" + facetName + "'. "
+            + "If 'NONE' is used, it must be the only include.");
+      }
+      return EnumSet.noneOf(FacetRangeOther.class);
+    }
+    if (others.contains(FacetRangeOther.ALL)) {
+      if (others.size() > 1) {
+        throw new SolrException(ErrorCode.BAD_REQUEST, "Include value 'ALL' is used with other includes in a range facet '" + facetName + "'. "
+            + "If 'ALL' is used, it must be the only include.");
+      }
+      return EnumSet.of(FacetRangeOther.BEFORE, FacetRangeOther.BETWEEN, FacetRangeOther.AFTER);
+    }
+    return others;
+  }
+
+  /*
+   * Query Facets
+   */
+  
+  private static QueryFacet constructQueryFacet(String name, AnalyticsQueryFacetRequest facetRequest) throws SolrException {
+    if (facetRequest.queries == null || facetRequest.queries.size() == 0) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Query Facets must be contain at least 1 query to facet over, '" + name + "' does not.");
+    }
+    
+    // The first param must be the facet name
+    return new QueryFacet(name, facetRequest.queries);
+  }
+  
+  /*
+   * Facet Sorting
+   */
+  
+  private static FacetSortSpecification constructSort(AnalyticsSortRequest sortRequest, Map<String, AnalyticsExpression> expressions) throws SolrException {
+    if (sortRequest.criteria == null || sortRequest.criteria.size() == 0) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, "Sorts must be given at least 1 criteria.");
+    }
+
+    return new FacetSortSpecification(constructSortCriteria(sortRequest.criteria, expressions), sortRequest.limit, sortRequest.offset);
+  }
+
+  private static FacetResultsComparator constructSortCriteria(List<AnalyticsSortCriteriaRequest> criteria, Map<String, AnalyticsExpression> expressions) {
+    ArrayList<FacetResultsComparator> comparators = new ArrayList<>();
+    for (AnalyticsSortCriteriaRequest criterion : criteria) {
+      FacetResultsComparator comparator;
+      if (criterion instanceof AnalyticsExpressionSortRequest) {
+        comparator = constructExpressionSortCriteria((AnalyticsExpressionSortRequest) criterion, expressions);
+      } else if (criterion instanceof AnalyticsFacetValueSortRequest) {
+        comparator = constructFacetValueSortCriteria((AnalyticsFacetValueSortRequest) criterion);
+      } else {
+        // Shouldn't happen
+        throw new SolrException(ErrorCode.BAD_REQUEST,"Sort Criteria must either be expressions or facetValues, '" + criterion.getClass().getName() + "' given.");
+      }
+      if (criterion.direction != null && criterion.direction.length() > 0) {
+        if (sortAscending.test(criterion.direction)) {
+          comparator.setDirection(true);
+        } else if (sortDescending.test(criterion.direction)) {
+          comparator.setDirection(false);
+        } else {
+          throw new SolrException(ErrorCode.BAD_REQUEST,"Sort direction '" + criterion.direction + " is not a recognized direction.");
+        }
+      }
+      comparators.add(comparator);
+    }
+    return DelegatingComparator.joinComparators(comparators);
+  }
+  
+  private static FacetResultsComparator constructExpressionSortCriteria(AnalyticsExpressionSortRequest criterion, Map<String, AnalyticsExpression> expressions) {
+    if (criterion.expression == null || criterion.expression.length() == 0) {
+      throw new SolrException(ErrorCode.BAD_REQUEST,"Expression Sorts must contain an expression parameter, none given.");
+    }
+
+    AnalyticsExpression expression = expressions.get(criterion.expression);
+    if (expression == null) {
+      throw new SolrException(ErrorCode.BAD_REQUEST,"Sort Expression not defined within the grouping: " + criterion.expression);
+    }
+    if (!(expression.getExpression() instanceof ComparableValue)) {
+      throw new SolrException(ErrorCode.BAD_REQUEST,"Expression Sorts must be comparable, the following is not: " + criterion.expression);
+    }
+    return ((ComparableValue)expression.getExpression()).getObjectComparator(expression.getName());
+  }
+  
+  private static FacetResultsComparator constructFacetValueSortCriteria(AnalyticsFacetValueSortRequest criterion) {
+    return new FacetValueComparator();
+  }
+}