You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2017/01/19 15:32:10 UTC

[01/38] lucene-solr:jira/solr-9857: SOLR-9906: Fix dodgy test check

Repository: lucene-solr
Updated Branches:
  refs/heads/jira/solr-9857 197bf0013 -> aef1f73a3


SOLR-9906: Fix dodgy test check


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

Branch: refs/heads/jira/solr-9857
Commit: efc7ee0f0c9154fe58671601fdc053540c97ff62
Parents: 478de2a
Author: Alan Woodward <ro...@apache.org>
Authored: Mon Jan 16 15:24:02 2017 +0000
Committer: Alan Woodward <ro...@apache.org>
Committed: Mon Jan 16 15:24:02 2017 +0000

----------------------------------------------------------------------
 .../java/org/apache/solr/cloud/AbstractDistribZkTestBase.java   | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/efc7ee0f/solr/test-framework/src/java/org/apache/solr/cloud/AbstractDistribZkTestBase.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractDistribZkTestBase.java b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractDistribZkTestBase.java
index 0669cbe..7141eed 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractDistribZkTestBase.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractDistribZkTestBase.java
@@ -29,7 +29,6 @@ import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.Slice;
-import org.apache.solr.common.cloud.Slice.State;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.core.Diagnostics;
@@ -240,8 +239,8 @@ public abstract class AbstractDistribZkTestBase extends BaseDistributedSearchTes
       ClusterState clusterState = zkStateReader.getClusterState();
       DocCollection coll = clusterState.getCollection("collection1");
       Slice slice = coll.getSlice(shardName);
-      if (slice.getLeader() != null && !slice.getLeader().equals(oldLeader) && slice.getState() == State.ACTIVE) {
-        log.info("Old leader {}, new leader. New leader got elected in {} ms", oldLeader, slice.getLeader(),timeOut.timeElapsed(MILLISECONDS) );
+      if (slice.getLeader() != null && !slice.getLeader().equals(oldLeader) && slice.getLeader().getState() == Replica.State.ACTIVE) {
+        log.info("Old leader {}, new leader {}. New leader got elected in {} ms", oldLeader, slice.getLeader(),timeOut.timeElapsed(MILLISECONDS) );
         break;
       }
 


[15/38] lucene-solr:jira/solr-9857: SOLR-9976: Fix init bug in SegmentsInfoRequestHandlerTest

Posted by ab...@apache.org.
SOLR-9976: Fix init bug in SegmentsInfoRequestHandlerTest


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

Branch: refs/heads/jira/solr-9857
Commit: 39eec660ca79b62947321390e07e83d84be419e5
Parents: e816fbe
Author: Chris Hostetter <ho...@apache.org>
Authored: Tue Jan 17 14:42:41 2017 -0700
Committer: Chris Hostetter <ho...@apache.org>
Committed: Tue Jan 17 14:42:41 2017 -0700

----------------------------------------------------------------------
 solr/CHANGES.txt                                |  5 +++++
 .../admin/SegmentsInfoRequestHandlerTest.java   | 20 +++++++++++++-------
 2 files changed, 18 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/39eec660/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 5b96c20..42be8a2 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -90,6 +90,11 @@ Jetty 9.3.14.v20161028
 Detailed Change List
 ----------------------
 
+Bug Fixes
+----------------------
+
+* SOLR-9976: Fix init bug in SegmentsInfoRequestHandlerTest (hossman)
+
 Optimizations
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/39eec660/solr/core/src/test/org/apache/solr/handler/admin/SegmentsInfoRequestHandlerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/SegmentsInfoRequestHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/SegmentsInfoRequestHandlerTest.java
index 885e419..1355e56 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/SegmentsInfoRequestHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/SegmentsInfoRequestHandlerTest.java
@@ -16,9 +16,11 @@
  */
 package org.apache.solr.handler.admin;
 
+import org.apache.lucene.index.LogDocMergePolicy;
 import org.apache.lucene.util.Version;
+import org.apache.solr.index.LogDocMergePolicyFactory;
 import org.apache.solr.util.AbstractSolrTestCase;
-import org.junit.Before;
+
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -32,13 +34,17 @@ public class SegmentsInfoRequestHandlerTest extends AbstractSolrTestCase {
   
   @BeforeClass
   public static void beforeClass() throws Exception {
-    System.setProperty("enable.update.log", "false");
-    System.setProperty("solr.tests.useMergePolicy", "false");
-    initCore("solrconfig.xml", "schema12.xml");
-  }
 
-  @Before
-  public void before() throws Exception {
+    // we need a consistent segmentation to ensure we don't get a random
+    // merge that reduces the total num docs in all segments, or the number of deletes
+    //
+    systemSetPropertySolrTestsMergePolicy(LogDocMergePolicy.class.getName());
+    systemSetPropertySolrTestsMergePolicyFactory(LogDocMergePolicyFactory.class.getName());
+    
+    System.setProperty("enable.update.log", "false"); // no _version_ in our schema
+    initCore("solrconfig.xml", "schema12.xml"); // segments API shouldn't depend on _version_ or ulog
+    
+    // build up an index with at least 2 segments and some deletes
     for (int i = 0; i < DOC_COUNT; i++) {
       assertU(adoc("id","SOLR100" + i, "name","Apache Solr:" + i));
     }


[04/38] lucene-solr:jira/solr-9857: Merge the two problem sections in org.eclipse.jdt.core.prefs settings.

Posted by ab...@apache.org.
Merge the two problem sections in org.eclipse.jdt.core.prefs settings.


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

Branch: refs/heads/jira/solr-9857
Commit: 205f9cc59ed001e6d262930482b88e723e2ea3f8
Parents: 2301900
Author: Christine Poerschke <cp...@apache.org>
Authored: Mon Jan 16 18:23:12 2017 +0000
Committer: Christine Poerschke <cp...@apache.org>
Committed: Mon Jan 16 18:42:07 2017 +0000

----------------------------------------------------------------------
 dev-tools/eclipse/dot.settings/org.eclipse.jdt.core.prefs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/205f9cc5/dev-tools/eclipse/dot.settings/org.eclipse.jdt.core.prefs
----------------------------------------------------------------------
diff --git a/dev-tools/eclipse/dot.settings/org.eclipse.jdt.core.prefs b/dev-tools/eclipse/dot.settings/org.eclipse.jdt.core.prefs
index 6f6533a..0f0b112 100644
--- a/dev-tools/eclipse/dot.settings/org.eclipse.jdt.core.prefs
+++ b/dev-tools/eclipse/dot.settings/org.eclipse.jdt.core.prefs
@@ -4,6 +4,7 @@ org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
 org.eclipse.jdt.core.compiler.compliance=1.8
 org.eclipse.jdt.core.compiler.doc.comment.support=enabled
 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=error
 org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
 org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error
 org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
@@ -18,6 +19,9 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore
 org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
 org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
 org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error
+org.eclipse.jdt.core.compiler.problem.unusedImport=error
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
 org.eclipse.jdt.core.compiler.source=1.8
 org.eclipse.jdt.core.compiler.taskCaseSensitive=enabled
@@ -304,7 +308,3 @@ org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
 org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
 org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
 org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=error
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error
-org.eclipse.jdt.core.compiler.problem.unusedImport=error
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error


[30/38] lucene-solr:jira/solr-9857: SOLR-8396: Add support for PointFields in Solr

Posted by ab...@apache.org.
SOLR-8396: Add support for PointFields in Solr


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

Branch: refs/heads/jira/solr-9857
Commit: 57934ba4480d71218c7f60d0417dbae9d26188d0
Parents: a89560b
Author: Tomas Fernandez Lobbe <tf...@apache.org>
Authored: Wed Jan 18 17:27:21 2017 -0800
Committer: Tomas Fernandez Lobbe <tf...@apache.org>
Committed: Wed Jan 18 17:27:21 2017 -0800

----------------------------------------------------------------------
 lucene/common-build.xml                         |    1 +
 solr/CHANGES.txt                                |    4 +
 .../solr/handler/admin/LukeRequestHandler.java  |    7 +-
 .../solr/handler/component/FacetComponent.java  |   12 +-
 .../solr/handler/component/QueryComponent.java  |   25 +-
 .../handler/component/RangeFacetProcessor.java  |    3 +-
 .../handler/component/RangeFacetRequest.java    |   31 +-
 .../solr/handler/component/StatsComponent.java  |    6 +
 .../handler/component/StatsValuesFactory.java   |    2 +-
 .../solr/index/SlowCompositeReaderWrapper.java  |    3 -
 .../org/apache/solr/request/IntervalFacets.java |    4 +
 .../org/apache/solr/request/SimpleFacets.java   |   37 +-
 .../org/apache/solr/response/DocsStreamer.java  |    8 +
 .../apache/solr/schema/DoublePointField.java    |  187 +++
 .../java/org/apache/solr/schema/FieldType.java  |    9 +-
 .../org/apache/solr/schema/FloatPointField.java |  187 +++
 .../org/apache/solr/schema/IntPointField.java   |  186 +++
 .../org/apache/solr/schema/LongPointField.java  |  186 +++
 .../java/org/apache/solr/schema/PointField.java |  233 +++
 .../org/apache/solr/schema/SchemaField.java     |   10 +
 .../apache/solr/search/SolrIndexSearcher.java   |   44 +-
 .../apache/solr/search/TermQParserPlugin.java   |   10 +-
 .../apache/solr/search/TermsQParserPlugin.java  |   10 +
 .../apache/solr/search/facet/FacetRange.java    |   28 +-
 .../DocumentExpressionDictionaryFactory.java    |   12 +-
 .../conf/schema-distrib-interval-faceting.xml   |   14 +-
 .../conf/schema-docValuesFaceting.xml           |   12 +
 .../solr/collection1/conf/schema-point.xml      |   88 ++
 .../solr/collection1/conf/schema-sorts.xml      |   44 +-
 .../test-files/solr/collection1/conf/schema.xml |   26 +-
 .../solr/collection1/conf/schema11.xml          |   19 +-
 .../solr/collection1/conf/schema12.xml          |   15 +-
 .../solr/collection1/conf/schema_latest.xml     |   21 +-
 .../apache/solr/TestDistributedGrouping.java    |   10 +-
 .../core/src/test/org/apache/solr/TestJoin.java |    6 +-
 .../org/apache/solr/TestRandomDVFaceting.java   |    8 +
 .../org/apache/solr/TestRandomFaceting.java     |   12 +-
 .../apache/solr/cloud/TestCloudPivotFacet.java  |    2 +
 .../handler/XsltUpdateRequestHandlerTest.java   |    2 +-
 .../handler/admin/LukeRequestHandlerTest.java   |    8 +-
 .../handler/component/TestExpandComponent.java  |    8 +-
 .../apache/solr/request/TestFacetMethods.java   |   12 +
 .../org/apache/solr/schema/TestPointFields.java | 1472 ++++++++++++++++++
 .../solr/search/TestCollapseQParserPlugin.java  |    4 +-
 .../solr/search/TestMaxScoreQueryParser.java    |    2 +-
 .../search/TestRandomCollapseQParserPlugin.java |    2 +
 .../apache/solr/search/TestSolrQueryParser.java |    8 +
 .../solr/search/facet/TestJsonFacets.java       |    2 +
 .../java/org/apache/solr/SolrTestCaseJ4.java    |   44 +-
 49 files changed, 2979 insertions(+), 107 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/lucene/common-build.xml
----------------------------------------------------------------------
diff --git a/lucene/common-build.xml b/lucene/common-build.xml
index 87d2e0a..48cf457 100644
--- a/lucene/common-build.xml
+++ b/lucene/common-build.xml
@@ -1073,6 +1073,7 @@
                 <propertyref prefix="tests.leaveTemporary" />
                 <propertyref prefix="tests.leavetemporary" />
                 <propertyref prefix="solr.test.leavetmpdir" />
+                <propertyref prefix="solr.tests.preferPointFields"/>
             </syspropertyset>
 
             <!-- Pass randomized settings to the forked JVM. -->

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 205c7bc..82c3d2b 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -74,6 +74,10 @@ Optimizations
 * SOLR-9584: Support Solr being proxied with another endpoint than default /solr, by using relative links
   in AdminUI javascripts (Yun Jie Zhou via janhoy)
 
+Other Changes
+----------------------
+* SOLR-8396: Add support for PointFields in Solr (Ishan Chattopadhyaya, Tom�s Fern�ndez L�bbe)
+
 ==================  6.5.0 ==================
 
 Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java
index 50f46ef..7f08684 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java
@@ -289,8 +289,6 @@ public class LukeRequestHandler extends RequestHandlerBase
       f.add( "schema", getFieldFlags( sfield ) );
       f.add( "flags", getFieldFlags( field ) );
 
-      Term t = new Term(field.name(), ftype!=null ? ftype.storedToIndexed(field) : field.stringValue());
-
       f.add( "value", (ftype==null)?null:ftype.toExternal( field ) );
 
       // TODO: this really should be "stored"
@@ -301,7 +299,10 @@ public class LukeRequestHandler extends RequestHandlerBase
         f.add( "binary", Base64.byteArrayToBase64(bytes.bytes, bytes.offset, bytes.length));
       }
       f.add( "boost", field.boost() );
-      f.add( "docFreq", t.text()==null ? 0 : reader.docFreq( t ) ); // this can be 0 for non-indexed fields
+      if (!ftype.isPointField()) {
+        Term t = new Term(field.name(), ftype!=null ? ftype.storedToIndexed(field) : field.stringValue());
+        f.add( "docFreq", t.text()==null ? 0 : reader.docFreq( t ) ); // this can be 0 for non-indexed fields
+      }// TODO: Calculate docFreq for point fields
 
       // If we have a term vector, return that
       if( field.fieldType().storeTermVectors() ) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java b/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
index 1cc05ab..bcff0c2 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
@@ -33,6 +33,7 @@ import java.util.Map.Entry;
 
 import org.apache.commons.lang.ArrayUtils;
 import org.apache.commons.lang.StringUtils;
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.FixedBitSet;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
@@ -47,6 +48,7 @@ import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.request.SimpleFacets;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.PointField;
 import org.apache.solr.search.QueryParsing;
 import org.apache.solr.search.SyntaxError;
 import org.apache.solr.search.facet.FacetDebugInfo;
@@ -1477,7 +1479,13 @@ public class FacetComponent extends SearchComponent {
           if (sfc == null) {
             sfc = new ShardFacetCount();
             sfc.name = name;
-            sfc.indexed = ftype == null ? sfc.name : ftype.toInternal(sfc.name);
+            if (ftype == null) {
+              sfc.indexed = null;
+            } else if (ftype.isPointField()) {
+              sfc.indexed = ((PointField)ftype).toInternalByteRef(sfc.name);
+            } else {
+              sfc.indexed = new BytesRef(ftype.toInternal(sfc.name));
+            }
             sfc.termNum = termNum++;
             counts.put(name, sfc);
           }
@@ -1553,7 +1561,7 @@ public class FacetComponent extends SearchComponent {
   public static class ShardFacetCount {
     public String name;
     // the indexed form of the name... used for comparisons
-    public String indexed; 
+    public BytesRef indexed; 
     public long count;
     public int termNum; // term number starting at 0 (used in bit arrays)
     

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
index 88ff731..c357202 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
@@ -185,6 +185,11 @@ public class QueryComponent extends SearchComponent
       }
 
       rb.setSortSpec( parser.getSortSpec(true) );
+      for (SchemaField sf:rb.getSortSpec().getSchemaFields()) {
+        if (sf != null && sf.getType().isPointField() && !sf.hasDocValues()) {
+          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,"Can't sort on a point field without docValues");
+        }
+      }
       rb.setQparser(parser);
 
       final String cursorStr = rb.req.getParams().get(CursorMarkParams.CURSOR_MARK_PARAM);
@@ -335,11 +340,21 @@ public class QueryComponent extends SearchComponent
       List<String> idArr = StrUtils.splitSmart(ids, ",", true);
       int[] luceneIds = new int[idArr.size()];
       int docs = 0;
-      for (int i=0; i<idArr.size(); i++) {
-        int id = searcher.getFirstMatch(
-                new Term(idField.getName(), idField.getType().toInternal(idArr.get(i))));
-        if (id >= 0)
-          luceneIds[docs++] = id;
+      if (idField.getType().isPointField()) {
+        for (int i=0; i<idArr.size(); i++) {
+          int id = searcher.search(
+              idField.getType().getFieldQuery(null, idField, idArr.get(i)), 1).scoreDocs[0].doc;
+          if (id >= 0) {
+            luceneIds[docs++] = id;
+          }
+        }
+      } else {
+        for (int i=0; i<idArr.size(); i++) {
+          int id = searcher.getFirstMatch(
+                  new Term(idField.getName(), idField.getType().toInternal(idArr.get(i))));
+          if (id >= 0)
+            luceneIds[docs++] = id;
+        }
       }
 
       DocListAndSet res = new DocListAndSet();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/handler/component/RangeFacetProcessor.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/RangeFacetProcessor.java b/solr/core/src/java/org/apache/solr/handler/component/RangeFacetProcessor.java
index 731d224..f8ab7b7 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/RangeFacetProcessor.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/RangeFacetProcessor.java
@@ -55,7 +55,6 @@ public class RangeFacetProcessor extends SimpleFacets {
    *
    * @see org.apache.solr.common.params.FacetParams#FACET_RANGE
    */
-  @SuppressWarnings("unchecked")
   public NamedList<Object> getFacetRangeCounts() throws IOException, SyntaxError {
     final NamedList<Object> resOuter = new SimpleOrderedMap<>();
 
@@ -92,7 +91,7 @@ public class RangeFacetProcessor extends SimpleFacets {
     final FieldType ft = sf.getType();
 
     if (method.equals(FacetRangeMethod.DV)) {
-      assert ft instanceof TrieField;
+      assert ft instanceof TrieField || ft.isPointField();
       resOuter.add(key, getFacetRangeCountsDocValues(rangeFacetRequest));
     } else {
       resOuter.add(key, getFacetRangeCounts(rangeFacetRequest));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/handler/component/RangeFacetRequest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/RangeFacetRequest.java b/solr/core/src/java/org/apache/solr/handler/component/RangeFacetRequest.java
index 8c0c381..f129e73 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/RangeFacetRequest.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/RangeFacetRequest.java
@@ -34,6 +34,7 @@ import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.schema.DateRangeField;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.PointField;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.TrieDateField;
 import org.apache.solr.schema.TrieField;
@@ -91,6 +92,11 @@ public class RangeFacetRequest extends FacetComponent.FacetBase {
           DateRangeField.class + "'. Will use method '" + FacetParams.FacetRangeMethod.FILTER + "' instead");
       method = FacetParams.FacetRangeMethod.FILTER;
     }
+    if (method.equals(FacetParams.FacetRangeMethod.DV) && !schemaField.hasDocValues() && (schemaField.getType().isPointField())) {
+      log.warn("Range facet method '" + FacetParams.FacetRangeMethod.DV + "' is not supported on PointFields without docValues." +
+          "Will use method '" + FacetParams.FacetRangeMethod.FILTER + "' instead");
+      method = FacetParams.FacetRangeMethod.FILTER;
+    }
 
     this.start = required.getFieldParam(facetOn, FacetParams.FACET_RANGE_START);
     this.end = required.getFieldParam(facetOn, FacetParams.FACET_RANGE_END);
@@ -159,10 +165,33 @@ public class RangeFacetRequest extends FacetComponent.FacetBase {
         default:
           throw new SolrException
               (SolrException.ErrorCode.BAD_REQUEST,
-                  "Unable to range facet on tried field of unexpected type:" + this.facetOn);
+                  "Unable to range facet on Trie field of unexpected type:" + this.facetOn);
       }
     } else if (ft instanceof DateRangeField) {
       calc = new DateRangeEndpointCalculator(this, null);
+    } else if (ft.isPointField()) {
+      final PointField pointField = (PointField) ft;
+      switch (pointField.getType()) {
+        case FLOAT:
+          calc = new FloatRangeEndpointCalculator(this);
+          break;
+        case DOUBLE:
+          calc = new DoubleRangeEndpointCalculator(this);
+          break;
+        case INTEGER:
+          calc = new IntegerRangeEndpointCalculator(this);
+          break;
+        case LONG:
+          calc = new LongRangeEndpointCalculator(this);
+          break;
+        case DATE:
+          calc = new DateRangeEndpointCalculator(this, null);
+          break;
+        default:
+          throw new SolrException
+              (SolrException.ErrorCode.BAD_REQUEST,
+                  "Unable to range facet on Point field of unexpected type:" + this.facetOn);
+      }
     } else {
       throw new SolrException
           (SolrException.ErrorCode.BAD_REQUEST,

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/handler/component/StatsComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/StatsComponent.java b/solr/core/src/java/org/apache/solr/handler/component/StatsComponent.java
index 68284c7..6a6e9be 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/StatsComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/StatsComponent.java
@@ -45,6 +45,12 @@ public class StatsComponent extends SearchComponent {
       rb.setNeedDocSet( true );
       rb.doStats = true;
       rb._statsInfo = new StatsInfo(rb);
+      for (StatsField statsField : rb._statsInfo.getStatsFields()) {
+        if (statsField.getSchemaField() != null && statsField.getSchemaField().getType().isPointField() && !statsField.getSchemaField().hasDocValues()) {
+          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, 
+              "Can't calculate stats on a PointField without docValues");
+        }
+      }
     }
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/handler/component/StatsValuesFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/StatsValuesFactory.java b/solr/core/src/java/org/apache/solr/handler/component/StatsValuesFactory.java
index 8a35ee0..7605f73 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/StatsValuesFactory.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/StatsValuesFactory.java
@@ -65,7 +65,7 @@ public class StatsValuesFactory {
     
     if (TrieDateField.class.isInstance(fieldType)) {
       return new DateStatsValues(statsField);
-    } else if (TrieField.class.isInstance(fieldType)) {
+    } else if (TrieField.class.isInstance(fieldType) || PointField.class.isInstance(fieldType)) {
       return new NumericStatsValues(statsField);
     } else if (StrField.class.isInstance(fieldType)) {
       return new StringStatsValues(statsField);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/index/SlowCompositeReaderWrapper.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/index/SlowCompositeReaderWrapper.java b/solr/core/src/java/org/apache/solr/index/SlowCompositeReaderWrapper.java
index 5031faf..12f5bd1 100644
--- a/solr/core/src/java/org/apache/solr/index/SlowCompositeReaderWrapper.java
+++ b/solr/core/src/java/org/apache/solr/index/SlowCompositeReaderWrapper.java
@@ -65,9 +65,6 @@ public final class SlowCompositeReaderWrapper extends LeafReader {
   SlowCompositeReaderWrapper(CompositeReader reader, boolean merging) throws IOException {
     super();
     in = reader;
-    if (getFieldInfos().hasPointValues()) {
-      throw new IllegalArgumentException("cannot wrap points");
-    }
     fields = MultiFields.getFields(in);
     in.registerParentReader(this);
     this.merging = merging;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/IntervalFacets.java b/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
index 14bf700..88e39fc 100644
--- a/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
+++ b/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
@@ -42,6 +42,7 @@ import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.request.IntervalFacets.FacetInterval;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.PointField;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.TrieDateField;
 import org.apache.solr.search.DocIterator;
@@ -625,6 +626,9 @@ public class IntervalFacets implements Iterable<FacetInterval> {
       if ("*".equals(value)) {
         return null;
       }
+      if (schemaField.getType().isPointField()) {
+        return ((PointField)schemaField.getType()).toInternalByteRef(value);
+      }
       return new BytesRef(schemaField.getType().toInternal(value));
     }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/SimpleFacets.java b/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
index 641b1f3..0d9cb29 100644
--- a/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
+++ b/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
@@ -411,6 +411,10 @@ public class SimpleFacets {
     
     NamedList<Integer> counts;
     SchemaField sf = searcher.getSchema().getField(field);
+    if (sf.getType().isPointField() && !sf.hasDocValues()) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, 
+          "Can't facet on a PointField without docValues");
+    }
     FieldType ft = sf.getType();
 
     // determine what type of faceting method to use
@@ -579,6 +583,10 @@ public class SimpleFacets {
    static FacetMethod selectFacetMethod(SchemaField field, FacetMethod method, Integer mincount) {
 
      FieldType type = field.getType();
+     if (type.isPointField()) {
+       // Only FCS is supported for PointFields for now
+       return FacetMethod.FCS;
+     }
 
      /*The user did not specify any preference*/
      if (method == null) {
@@ -810,12 +818,20 @@ public class SimpleFacets {
    * @param terms a list of term values (in the specified field) to compute the counts for 
    */
   protected NamedList<Integer> getListedTermCounts(String field, final ParsedParams parsed, List<String> terms) throws IOException {
-    FieldType ft = searcher.getSchema().getFieldType(field);
+    SchemaField sf = searcher.getSchema().getField(field);
+    FieldType ft = sf.getType();
     NamedList<Integer> res = new NamedList<>();
-    for (String term : terms) {
-      String internal = ft.toInternal(term);
-      int count = searcher.numDocs(new TermQuery(new Term(field, internal)), parsed.docs);
-      res.add(term, count);
+    if (ft.isPointField()) {
+      for (String term : terms) {
+        int count = searcher.numDocs(ft.getFieldQuery(null, sf, term), parsed.docs);
+        res.add(term, count);
+      }
+    } else {
+      for (String term : terms) {
+        String internal = ft.toInternal(term);
+        int count = searcher.numDocs(new TermQuery(new Term(field, internal)), parsed.docs);
+        res.add(term, count);
+      }
     }
     return res;    
   }
@@ -848,7 +864,7 @@ public class SimpleFacets {
   public NamedList<Integer> getFacetTermEnumCounts(SolrIndexSearcher searcher, DocSet docs, String field, int offset, int limit, int mincount, boolean missing, 
                                       String sort, String prefix, String contains, boolean ignoreCase, boolean intersectsCheck)
     throws IOException {
-
+    
     /* :TODO: potential optimization...
     * cache the Terms with the highest docFreq and try them first
     * don't enum if we get our max from them
@@ -864,10 +880,12 @@ public class SimpleFacets {
       fastForRandomSet = new HashDocSet(sset.getDocs(), 0, sset.size());
     }
 
-
     IndexSchema schema = searcher.getSchema();
-    LeafReader r = searcher.getSlowAtomicReader();
     FieldType ft = schema.getFieldType(field);
+    assert !ft.isPointField(): "Point Fields don't support enum method";
+    
+    LeafReader r = searcher.getSlowAtomicReader();
+    
 
     boolean sortByCount = sort.equals("count") || sort.equals("true");
     final int maxsize = limit>=0 ? offset+limit : Integer.MAX_VALUE-1;
@@ -1082,6 +1100,9 @@ public class SimpleFacets {
       if (parsed.params.getBool(GroupParams.GROUP_FACET, false)) {
         throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Interval Faceting can't be used with " + GroupParams.GROUP_FACET);
       }
+      if (schemaField.getType().isPointField() && !schemaField.hasDocValues()) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Can't use interval faceting on a PointField without docValues");
+      }
       
       SimpleOrderedMap<Integer> fieldResults = new SimpleOrderedMap<Integer>();
       res.add(parsed.key, fieldResults);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/response/DocsStreamer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/DocsStreamer.java b/solr/core/src/java/org/apache/solr/response/DocsStreamer.java
index bee699c..ef0b0c7 100644
--- a/solr/core/src/java/org/apache/solr/response/DocsStreamer.java
+++ b/solr/core/src/java/org/apache/solr/response/DocsStreamer.java
@@ -31,8 +31,12 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.response.transform.DocTransformer;
 import org.apache.solr.schema.BinaryField;
 import org.apache.solr.schema.BoolField;
+import org.apache.solr.schema.DoublePointField;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.FloatPointField;
 import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.IntPointField;
+import org.apache.solr.schema.LongPointField;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.StrField;
 import org.apache.solr.schema.TextField;
@@ -213,6 +217,10 @@ public class DocsStreamer implements Iterator<SolrDocument> {
     KNOWN_TYPES.add(TrieDoubleField.class);
     KNOWN_TYPES.add(TrieDateField.class);
     KNOWN_TYPES.add(BinaryField.class);
+    KNOWN_TYPES.add(IntPointField.class);
+    KNOWN_TYPES.add(LongPointField.class);
+    KNOWN_TYPES.add(DoublePointField.class);
+    KNOWN_TYPES.add(FloatPointField.class);
     // We do not add UUIDField because UUID object is not a supported type in JavaBinCodec
     // and if we write UUIDField.toObject, we wouldn't know how to handle it in the client side
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/schema/DoublePointField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/DoublePointField.java b/solr/core/src/java/org/apache/solr/schema/DoublePointField.java
new file mode 100644
index 0000000..c393dfe
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/schema/DoublePointField.java
@@ -0,0 +1,187 @@
+/*
+ * 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.schema;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+
+import org.apache.lucene.document.DoublePoint;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.legacy.LegacyNumericType;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.queries.function.valuesource.DoubleFieldSource;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.solr.search.QParser;
+import org.apache.solr.uninverting.UninvertingReader.Type;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@code PointField} implementation for {@code Double} values.
+ * @see PointField
+ * @see DoublePoint
+ */
+public class DoublePointField extends PointField implements DoubleValueFieldType {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Override
+  public Object toNativeType(Object val) {
+    if (val == null) return null;
+    if (val instanceof Number) return ((Number) val).doubleValue();
+    if (val instanceof String) return Double.parseDouble((String) val);
+    return super.toNativeType(val);
+  }
+
+  @Override
+  public Query getRangeQuery(QParser parser, SchemaField field, String min, String max, boolean minInclusive,
+      boolean maxInclusive) {
+    double actualMin, actualMax;
+    if (min == null) {
+      actualMin = Double.NEGATIVE_INFINITY;
+    } else {
+      actualMin = Double.parseDouble(min);
+      if (!minInclusive) {
+        actualMin = DoublePoint.nextUp(actualMin);
+      }
+    }
+    if (max == null) {
+      actualMax = Double.POSITIVE_INFINITY;
+    } else {
+      actualMax = Double.parseDouble(max);
+      if (!maxInclusive) {
+        actualMax = DoublePoint.nextDown(actualMax);
+      }
+    }
+    return DoublePoint.newRangeQuery(field.getName(), actualMin, actualMax);
+  }
+
+  @Override
+  public Object toObject(SchemaField sf, BytesRef term) {
+    return DoublePoint.decodeDimension(term.bytes, term.offset);
+  }
+  
+  @Override
+  public Object toObject(IndexableField f) {
+    final Number val = f.numericValue();
+    if (val != null) {
+      if (f.fieldType().stored() == false && f.fieldType().docValuesType() == DocValuesType.NUMERIC) {
+        return Double.longBitsToDouble(val.longValue());
+      } else {
+        return val;
+      }
+    } else {
+      throw new AssertionError("Unexpected state. Field: '" + f + "'");
+    }
+  }
+
+  @Override
+  protected Query getExactQuery(SchemaField field, String externalVal) {
+    return DoublePoint.newExactQuery(field.getName(), Double.parseDouble(externalVal));
+  }
+
+  @Override
+  public Query getSetQuery(QParser parser, SchemaField field, Collection<String> externalVal) {
+    assert externalVal.size() > 0;
+    double[] values = new double[externalVal.size()];
+    int i = 0;
+    for (String val:externalVal) {
+      values[i] = Double.parseDouble(val);
+      i++;
+    }
+    return DoublePoint.newSetQuery(field.getName(), values);
+  }
+
+  @Override
+  protected String indexedToReadable(BytesRef indexedForm) {
+    return Double.toString(DoublePoint.decodeDimension(indexedForm.bytes, indexedForm.offset));
+  }
+
+  @Override
+  public void readableToIndexed(CharSequence val, BytesRefBuilder result) {
+    result.grow(Double.BYTES);
+    result.setLength(Double.BYTES);
+    DoublePoint.encodeDimension(Double.parseDouble(val.toString()), result.bytes(), 0);
+  }
+
+  @Override
+  public SortField getSortField(SchemaField field, boolean top) {
+    field.checkSortability();
+
+    Object missingValue = null;
+    boolean sortMissingLast = field.sortMissingLast();
+    boolean sortMissingFirst = field.sortMissingFirst();
+
+    if (sortMissingLast) {
+      missingValue = top ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
+    } else if (sortMissingFirst) {
+      missingValue = top ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
+    }
+    SortField sf = new SortField(field.getName(), SortField.Type.DOUBLE, top);
+    sf.setMissingValue(missingValue);
+    return sf;
+  }
+
+  @Override
+  public Type getUninversionType(SchemaField sf) {
+    if (sf.multiValued()) {
+      throw new UnsupportedOperationException("MultiValued Point fields with DocValues is not currently supported");
+//      return Type.SORTED_DOUBLE;
+    } else {
+      return Type.DOUBLE_POINT;
+    }
+  }
+
+  @Override
+  public ValueSource getValueSource(SchemaField field, QParser qparser) {
+    field.checkFieldCacheSource();
+    return new DoubleFieldSource(field.getName());
+  }
+
+  @Override
+  public LegacyNumericType getNumericType() {
+    // TODO: refactor this to not use LegacyNumericType
+    return LegacyNumericType.DOUBLE;
+  }
+
+  @Override
+  public IndexableField createField(SchemaField field, Object value, float boost) {
+    if (!isFieldUsed(field)) return null;
+
+    if (boost != 1.0 && log.isTraceEnabled()) {
+      log.trace("Can't use document/field boost for PointField. Field: " + field.getName() + ", boost: " + boost);
+    }
+    double doubleValue = (value instanceof Number) ? ((Number) value).doubleValue() : Double.parseDouble(value.toString());
+    return new DoublePoint(field.getName(), doubleValue);
+  }
+
+  @Override
+  protected StoredField getStoredField(SchemaField sf, Object value) {
+    return new StoredField(sf.getName(), (Double) this.toNativeType(value));
+  }
+
+  @Override
+  public PointTypes getType() {
+    return PointTypes.DOUBLE;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/schema/FieldType.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/FieldType.java b/solr/core/src/java/org/apache/solr/schema/FieldType.java
index a5c898a..3922edc 100644
--- a/solr/core/src/java/org/apache/solr/schema/FieldType.java
+++ b/solr/core/src/java/org/apache/solr/schema/FieldType.java
@@ -126,6 +126,10 @@ public abstract class FieldType extends FieldProperties {
   public boolean isPolyField(){
     return false;
   }
+  
+  public boolean isPointField() {
+    return false;
+  }
 
   /**
    * Returns true if the fields' docValues should be used for obtaining stored value
@@ -395,7 +399,10 @@ public abstract class FieldType extends FieldProperties {
     return toInternal(val);
   }
 
-  /** Given the readable value, return the term value that will match it. */
+  /** Given the readable value, return the term value that will match it.
+   * This method will modify the size and length of the {@code result} 
+   * parameter and write from offset 0
+   */
   public void readableToIndexed(CharSequence val, BytesRefBuilder result) {
     final String internal = readableToIndexed(val.toString());
     result.copyChars(internal);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/schema/FloatPointField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/FloatPointField.java b/solr/core/src/java/org/apache/solr/schema/FloatPointField.java
new file mode 100644
index 0000000..766c6e9
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/schema/FloatPointField.java
@@ -0,0 +1,187 @@
+/*
+ * 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.schema;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+
+import org.apache.lucene.document.FloatPoint;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.legacy.LegacyNumericType;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.queries.function.valuesource.FloatFieldSource;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.solr.search.QParser;
+import org.apache.solr.uninverting.UninvertingReader.Type;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@code PointField} implementation for {@code Float} values.
+ * @see PointField
+ * @see FloatPoint
+ */
+public class FloatPointField extends PointField implements FloatValueFieldType {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Override
+  public Object toNativeType(Object val) {
+    if (val == null) return null;
+    if (val instanceof Number) return ((Number) val).floatValue();
+    if (val instanceof String) return Float.parseFloat((String) val);
+    return super.toNativeType(val);
+  }
+
+  @Override
+  public Query getRangeQuery(QParser parser, SchemaField field, String min, String max, boolean minInclusive,
+      boolean maxInclusive) {
+    float actualMin, actualMax;
+    if (min == null) {
+      actualMin = Float.NEGATIVE_INFINITY;
+    } else {
+      actualMin = Float.parseFloat(min);
+      if (!minInclusive) {
+        actualMin = FloatPoint.nextUp(actualMin);
+      }
+    }
+    if (max == null) {
+      actualMax = Float.POSITIVE_INFINITY;
+    } else {
+      actualMax = Float.parseFloat(max);
+      if (!maxInclusive) {
+        actualMax = FloatPoint.nextDown(actualMax);
+      }
+    }
+    return FloatPoint.newRangeQuery(field.getName(), actualMin, actualMax);
+  }
+
+  @Override
+  public Object toObject(SchemaField sf, BytesRef term) {
+    return FloatPoint.decodeDimension(term.bytes, term.offset);
+  }
+  
+  @Override
+  public Object toObject(IndexableField f) {
+    final Number val = f.numericValue();
+    if (val != null) {
+      if (f.fieldType().stored() == false && f.fieldType().docValuesType() == DocValuesType.NUMERIC) {
+        return Float.intBitsToFloat(val.intValue());
+      } else {
+        return val;
+      }
+    } else {
+      throw new AssertionError("Unexpected state. Field: '" + f + "'");
+    }
+  }
+
+  @Override
+  protected Query getExactQuery(SchemaField field, String externalVal) {
+    return FloatPoint.newExactQuery(field.getName(), Float.parseFloat(externalVal));
+  }
+
+  @Override
+  public Query getSetQuery(QParser parser, SchemaField field, Collection<String> externalVal) {
+    assert externalVal.size() > 0;
+    float[] values = new float[externalVal.size()];
+    int i = 0;
+    for (String val:externalVal) {
+      values[i] = Float.parseFloat(val);
+      i++;
+    }
+    return FloatPoint.newSetQuery(field.getName(), values);
+  }
+
+  @Override
+  protected String indexedToReadable(BytesRef indexedForm) {
+    return Float.toString(FloatPoint.decodeDimension(indexedForm.bytes, indexedForm.offset));
+  }
+
+  @Override
+  public void readableToIndexed(CharSequence val, BytesRefBuilder result) {
+    result.grow(Float.BYTES);
+    result.setLength(Float.BYTES);
+    FloatPoint.encodeDimension(Float.parseFloat(val.toString()), result.bytes(), 0);
+  }
+
+  @Override
+  public SortField getSortField(SchemaField field, boolean top) {
+    field.checkSortability();
+
+    Object missingValue = null;
+    boolean sortMissingLast = field.sortMissingLast();
+    boolean sortMissingFirst = field.sortMissingFirst();
+
+    if (sortMissingLast) {
+      missingValue = top ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
+    } else if (sortMissingFirst) {
+      missingValue = top ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY;
+    }
+    SortField sf = new SortField(field.getName(), SortField.Type.FLOAT, top);
+    sf.setMissingValue(missingValue);
+    return sf;
+  }
+
+  @Override
+  public Type getUninversionType(SchemaField sf) {
+    if (sf.multiValued()) {
+      throw new UnsupportedOperationException("MultiValued Point fields with DocValues is not currently supported");
+//      return Type.SORTED_FLOAT;
+    } else {
+      return Type.FLOAT_POINT;
+    }
+  }
+
+  @Override
+  public ValueSource getValueSource(SchemaField field, QParser qparser) {
+    field.checkFieldCacheSource();
+    return new FloatFieldSource(field.getName());
+  }
+
+  @Override
+  public LegacyNumericType getNumericType() {
+    // TODO: refactor this to not use LegacyNumericType
+    return LegacyNumericType.FLOAT;
+  }
+
+  @Override
+  public IndexableField createField(SchemaField field, Object value, float boost) {
+    if (!isFieldUsed(field)) return null;
+
+    if (boost != 1.0 && log.isTraceEnabled()) {
+      log.trace("Can't use document/field boost for PointField. Field: " + field.getName() + ", boost: " + boost);
+    }
+    float floatValue = (value instanceof Number) ? ((Number) value).floatValue() : Float.parseFloat(value.toString());
+    return new FloatPoint(field.getName(), floatValue);
+  }
+
+  @Override
+  protected StoredField getStoredField(SchemaField sf, Object value) {
+    return new StoredField(sf.getName(), (Float) this.toNativeType(value));
+  }
+  
+  @Override
+  public PointTypes getType() {
+    return PointTypes.FLOAT;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/schema/IntPointField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/IntPointField.java b/solr/core/src/java/org/apache/solr/schema/IntPointField.java
new file mode 100644
index 0000000..a7bab07
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/schema/IntPointField.java
@@ -0,0 +1,186 @@
+/*
+ * 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.schema;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+
+import org.apache.lucene.document.IntPoint;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.legacy.LegacyNumericType;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.queries.function.valuesource.IntFieldSource;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.solr.search.QParser;
+import org.apache.solr.uninverting.UninvertingReader.Type;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@code PointField} implementation for {@code Integer} values.
+ * @see PointField
+ * @see IntPoint
+ */
+public class IntPointField extends PointField implements IntValueFieldType {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Override
+  public Object toNativeType(Object val) {
+    if (val == null) return null;
+    if (val instanceof Number) return ((Number) val).intValue();
+    try {
+      if (val instanceof String) return Integer.parseInt((String) val);
+    } catch (NumberFormatException e) {
+      Float v = Float.parseFloat((String) val);
+      return v.intValue();
+    }
+    return super.toNativeType(val);
+  }
+
+  @Override
+  public Query getRangeQuery(QParser parser, SchemaField field, String min, String max, boolean minInclusive,
+      boolean maxInclusive) {
+    int actualMin, actualMax;
+    if (min == null) {
+      actualMin = Integer.MIN_VALUE;
+    } else {
+      actualMin = Integer.parseInt(min);
+      if (!minInclusive) {
+        actualMin++;
+      }
+    }
+    if (max == null) {
+      actualMax = Integer.MAX_VALUE;
+    } else {
+      actualMax = Integer.parseInt(max);
+      if (!maxInclusive) {
+        actualMax--;
+      }
+    }
+    return IntPoint.newRangeQuery(field.getName(), actualMin, actualMax);
+  }
+
+  @Override
+  public Object toObject(SchemaField sf, BytesRef term) {
+    return IntPoint.decodeDimension(term.bytes, term.offset);
+  }
+  
+  @Override
+  public Object toObject(IndexableField f) {
+    final Number val = f.numericValue();
+    if (val != null) {
+      return val;
+    } else {
+      throw new AssertionError("Unexpected state. Field: '" + f + "'");
+    }
+  }
+
+  @Override
+  protected Query getExactQuery(SchemaField field, String externalVal) {
+    return IntPoint.newExactQuery(field.getName(), Integer.parseInt(externalVal));
+  }
+  
+  @Override
+  public Query getSetQuery(QParser parser, SchemaField field, Collection<String> externalVal) {
+    assert externalVal.size() > 0;
+    int[] values = new int[externalVal.size()];
+    int i = 0;
+    for (String val:externalVal) {
+      values[i] = Integer.parseInt(val);
+      i++;
+    }
+    return IntPoint.newSetQuery(field.getName(), values);
+  }
+
+  @Override
+  protected String indexedToReadable(BytesRef indexedForm) {
+    return Integer.toString(IntPoint.decodeDimension(indexedForm.bytes, indexedForm.offset));
+  }
+
+  @Override
+  public void readableToIndexed(CharSequence val, BytesRefBuilder result) {
+    result.grow(Integer.BYTES);
+    result.setLength(Integer.BYTES);
+    IntPoint.encodeDimension(Integer.parseInt(val.toString()), result.bytes(), 0);
+  }
+
+  @Override
+  public SortField getSortField(SchemaField field, boolean top) {
+    field.checkSortability();
+
+    Object missingValue = null;
+    boolean sortMissingLast = field.sortMissingLast();
+    boolean sortMissingFirst = field.sortMissingFirst();
+
+    if (sortMissingLast) {
+      missingValue = top ? Integer.MIN_VALUE : Integer.MAX_VALUE;
+    } else if (sortMissingFirst) {
+      missingValue = top ? Integer.MAX_VALUE : Integer.MIN_VALUE;
+    }
+    SortField sf = new SortField(field.getName(), SortField.Type.INT, top);
+    sf.setMissingValue(missingValue);
+    return sf;
+  }
+
+  @Override
+  public Type getUninversionType(SchemaField sf) {
+    if (sf.multiValued()) {
+      throw new UnsupportedOperationException("MultiValued Point fields with DocValues is not currently supported");
+//      return Type.SORTED_INTEGER;
+    } else {
+      return Type.INTEGER_POINT;
+    }
+  }
+
+  @Override
+  public ValueSource getValueSource(SchemaField field, QParser qparser) {
+    field.checkFieldCacheSource();
+    return new IntFieldSource(field.getName());
+  }
+
+  @Override
+  public LegacyNumericType getNumericType() {
+    return LegacyNumericType.INT;
+  }
+
+  @Override
+  public IndexableField createField(SchemaField field, Object value, float boost) {
+    if (!isFieldUsed(field)) return null;
+
+    if (boost != 1.0 && log.isTraceEnabled()) {
+      log.trace("Can't use document/field boost for PointField. Field: " + field.getName() + ", boost: " + boost);
+    }
+    int intValue = (value instanceof Number) ? ((Number) value).intValue() : Integer.parseInt(value.toString());
+    return new IntPoint(field.getName(), intValue);
+  }
+
+  @Override
+  protected StoredField getStoredField(SchemaField sf, Object value) {
+    return new StoredField(sf.getName(), (Integer) this.toNativeType(value));
+  }
+
+  @Override
+  public PointTypes getType() {
+    return PointTypes.INTEGER;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/schema/LongPointField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/LongPointField.java b/solr/core/src/java/org/apache/solr/schema/LongPointField.java
new file mode 100644
index 0000000..f3fca3c
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/schema/LongPointField.java
@@ -0,0 +1,186 @@
+/*
+ * 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.schema;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+
+import org.apache.lucene.document.LongPoint;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.legacy.LegacyNumericType;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.queries.function.valuesource.LongFieldSource;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.solr.search.QParser;
+import org.apache.solr.uninverting.UninvertingReader.Type;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@code PointField} implementation for {@code Long} values.
+ * @see PointField
+ * @see LongPoint
+ */
+public class LongPointField extends PointField implements LongValueFieldType {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Override
+  public Object toNativeType(Object val) {
+    if (val == null) return null;
+    if (val instanceof Number) return ((Number) val).longValue();
+    try {
+      if (val instanceof String) return Long.parseLong((String) val);
+    } catch (NumberFormatException e) {
+      Double v = Double.parseDouble((String) val);
+      return v.longValue();
+    }
+    return super.toNativeType(val);
+  }
+
+  @Override
+  public Query getRangeQuery(QParser parser, SchemaField field, String min, String max, boolean minInclusive,
+      boolean maxInclusive) {
+    long actualMin, actualMax;
+    if (min == null) {
+      actualMin = Long.MIN_VALUE;
+    } else {
+      actualMin = Long.parseLong(min);
+      if (!minInclusive) {
+        actualMin++;
+      }
+    }
+    if (max == null) {
+      actualMax = Long.MAX_VALUE;
+    } else {
+      actualMax = Long.parseLong(max);
+      if (!maxInclusive) {
+        actualMax--;
+      }
+    }
+    return LongPoint.newRangeQuery(field.getName(), actualMin, actualMax);
+  }
+
+  @Override
+  public Object toObject(SchemaField sf, BytesRef term) {
+    return LongPoint.decodeDimension(term.bytes, term.offset);
+  }
+  
+  @Override
+  public Object toObject(IndexableField f) {
+    final Number val = f.numericValue();
+    if (val != null) {
+      return val;
+    } else {
+      throw new AssertionError("Unexpected state. Field: '" + f + "'");
+    }
+  }
+
+  @Override
+  protected Query getExactQuery(SchemaField field, String externalVal) {
+    return LongPoint.newExactQuery(field.getName(), Long.parseLong(externalVal));
+  }
+  
+  @Override
+  public Query getSetQuery(QParser parser, SchemaField field, Collection<String> externalVal) {
+    assert externalVal.size() > 0;
+    long[] values = new long[externalVal.size()];
+    int i = 0;
+    for (String val:externalVal) {
+      values[i] = Long.parseLong(val);
+      i++;
+    }
+    return LongPoint.newSetQuery(field.getName(), values);
+  }
+
+  @Override
+  protected String indexedToReadable(BytesRef indexedForm) {
+    return Long.toString(LongPoint.decodeDimension(indexedForm.bytes, indexedForm.offset));
+  }
+
+  @Override
+  public void readableToIndexed(CharSequence val, BytesRefBuilder result) {
+    result.grow(Long.BYTES);
+    result.setLength(Long.BYTES);
+    LongPoint.encodeDimension(Long.parseLong(val.toString()), result.bytes(), 0);
+  }
+
+  @Override
+  public SortField getSortField(SchemaField field, boolean top) {
+    field.checkSortability();
+
+    Object missingValue = null;
+    boolean sortMissingLast = field.sortMissingLast();
+    boolean sortMissingFirst = field.sortMissingFirst();
+
+    if (sortMissingLast) {
+      missingValue = top ? Long.MIN_VALUE : Long.MAX_VALUE;
+    } else if (sortMissingFirst) {
+      missingValue = top ? Long.MAX_VALUE : Long.MIN_VALUE;
+    }
+    SortField sf = new SortField(field.getName(), SortField.Type.LONG, top);
+    sf.setMissingValue(missingValue);
+    return sf;
+  }
+
+  @Override
+  public Type getUninversionType(SchemaField sf) {
+    if (sf.multiValued()) {
+      throw new UnsupportedOperationException("MultiValued Point fields with DocValues is not currently supported");
+//      return Type.SORTED_LONG;
+    } else {
+      return Type.LONG_POINT;
+    }
+  }
+
+  @Override
+  public ValueSource getValueSource(SchemaField field, QParser qparser) {
+    field.checkFieldCacheSource();
+    return new LongFieldSource(field.getName());
+  }
+
+  @Override
+  public LegacyNumericType getNumericType() {
+    return LegacyNumericType.LONG;
+  }
+
+  @Override
+  public IndexableField createField(SchemaField field, Object value, float boost) {
+    if (!isFieldUsed(field)) return null;
+
+    if (boost != 1.0 && log.isTraceEnabled()) {
+      log.trace("Can't use document/field boost for PointField. Field: " + field.getName() + ", boost: " + boost);
+    }
+    long longValue = (value instanceof Number) ? ((Number) value).longValue() : Long.parseLong(value.toString());
+    return new LongPoint(field.getName(), longValue);
+  }
+
+  @Override
+  protected StoredField getStoredField(SchemaField sf, Object value) {
+    return new StoredField(sf.getName(), (Long) this.toNativeType(value));
+  }
+
+  @Override
+  public PointTypes getType() {
+    return PointTypes.LONG;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/schema/PointField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/PointField.java b/solr/core/src/java/org/apache/solr/schema/PointField.java
new file mode 100644
index 0000000..a2dd8a8
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/schema/PointField.java
@@ -0,0 +1,233 @@
+/*
+ * 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.schema;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.lucene.document.NumericDocValuesField;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SortedSetSelector;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.lucene.util.CharsRef;
+import org.apache.lucene.util.CharsRefBuilder;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.response.TextResponseWriter;
+import org.apache.solr.search.QParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides field types to support for Lucene's {@link
+ * org.apache.lucene.document.IntPoint}, {@link org.apache.lucene.document.LongPoint}, {@link org.apache.lucene.document.FloatPoint} and
+ * {@link org.apache.lucene.document.DoublePoint}.
+ * See {@link org.apache.lucene.search.PointRangeQuery} for more details.
+ * It supports integer, float, long and double types. See subclasses for details.
+ * <br>
+ * {@code DocValues} are supported for single-value cases ({@code NumericDocValues}).
+ * {@code FieldCache} is not supported for {@code PointField}s, so sorting, faceting, etc on these fields require the use of {@code docValues="true"} in the schema.
+ */
+public abstract class PointField extends PrimitiveFieldType {
+  
+  public enum PointTypes {
+    INTEGER,
+    LONG,
+    FLOAT,
+    DOUBLE,
+    DATE
+  }
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Override
+  public boolean isPointField() {
+    return true;
+  }
+  
+  @Override
+  public final ValueSource getSingleValueSource(MultiValueSelector choice, SchemaField field, QParser parser) {
+    // trivial base case
+    if (!field.multiValued()) {
+      // single value matches any selector
+      return getValueSource(field, parser);
+    }
+
+    // Point fields don't support UninvertingReader. See SOLR-9202
+    if (!field.hasDocValues()) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+                              "docValues='true' is required to select '" + choice.toString() +
+                              "' value from multivalued field ("+ field.getName() +") at query time");
+    }
+    
+    // multivalued Point fields all use SortedSetDocValues, so we give a clean error if that's
+    // not supported by the specified choice, else we delegate to a helper
+    SortedSetSelector.Type selectorType = choice.getSortedSetSelectorType();
+    if (null == selectorType) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+                              choice.toString() + " is not a supported option for picking a single value"
+                              + " from the multivalued field: " + field.getName() +
+                              " (type: " + this.getTypeName() + ")");
+    }
+    
+    return getSingleValueSource(selectorType, field);
+  }
+
+  /**
+   * Helper method that will only be called for multivalued Point fields that have doc values.
+   * Default impl throws an error indicating that selecting a single value from this multivalued 
+   * field is not supported for this field type
+   *
+   * @param choice the selector Type to use, will never be null
+   * @param field the field to use, guaranteed to be multivalued.
+   * @see #getSingleValueSource(MultiValueSelector,SchemaField,QParser) 
+   */
+  protected ValueSource getSingleValueSource(SortedSetSelector.Type choice, SchemaField field) {
+    throw new UnsupportedOperationException("MultiValued Point fields with DocValues is not currently supported");
+  }
+
+  @Override
+  public boolean isTokenized() {
+    return false;
+  }
+
+  @Override
+  public boolean multiValuedFieldCache() {
+    return false;
+  }
+
+  /**
+   * @return the type of this field
+   */
+  public abstract PointTypes getType();
+  
+  @Override
+  public abstract Query getSetQuery(QParser parser, SchemaField field, Collection<String> externalVals);
+
+  @Override
+  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
+    if (!field.indexed() && field.hasDocValues()) {
+      // currently implemented as singleton range
+      return getRangeQuery(parser, field, externalVal, externalVal, true, true);
+    } else {
+      return getExactQuery(field, externalVal);
+    }
+  }
+
+  protected abstract Query getExactQuery(SchemaField field, String externalVal);
+
+  @Override
+  public String storedToReadable(IndexableField f) {
+    return toExternal(f);
+  }
+
+  @Override
+  public String toInternal(String val) {
+    throw new UnsupportedOperationException("Can't generate internal string in PointField. use PointField.toInternalByteRef");
+  }
+  
+  public BytesRef toInternalByteRef(String val) {
+    final BytesRefBuilder bytes = new BytesRefBuilder();
+    readableToIndexed(val, bytes);
+    return bytes.get();
+  }
+  
+  @Override
+  public void write(TextResponseWriter writer, String name, IndexableField f) throws IOException {
+    writer.writeVal(name, toObject(f));
+  }
+
+  @Override
+  public String storedToIndexed(IndexableField f) {
+    throw new UnsupportedOperationException("Not supported with PointFields");
+  }
+  
+  @Override
+  public CharsRef indexedToReadable(BytesRef indexedForm, CharsRefBuilder charsRef) {
+    final String value = indexedToReadable(indexedForm);
+    charsRef.grow(value.length());
+    charsRef.setLength(value.length());
+    value.getChars(0, charsRef.length(), charsRef.chars(), 0);
+    return charsRef.get();
+  }
+  
+  @Override
+  public String indexedToReadable(String indexedForm) {
+    return indexedToReadable(new BytesRef(indexedForm));
+  }
+  
+  protected abstract String indexedToReadable(BytesRef indexedForm);
+  
+  protected boolean isFieldUsed(SchemaField field) {
+    boolean indexed = field.indexed();
+    boolean stored = field.stored();
+    boolean docValues = field.hasDocValues();
+
+    if (!indexed && !stored && !docValues) {
+      if (log.isTraceEnabled()) {
+        log.trace("Ignoring unindexed/unstored field: " + field);
+      }
+      return false;
+    }
+    return true;
+  }
+
+  @Override
+  public List<IndexableField> createFields(SchemaField sf, Object value, float boost) {
+    if (!(sf.hasDocValues() || sf.stored())) {
+      return Collections.singletonList(createField(sf, value, boost));
+    }
+    List<IndexableField> fields = new ArrayList<>();
+    final IndexableField field = createField(sf, value, boost);
+    fields.add(field);
+    
+    if (sf.hasDocValues()) {
+      if (sf.multiValued()) {
+        throw new UnsupportedOperationException("MultiValued Point fields with DocValues is not currently supported. Field: '" + sf.getName() + "'");
+      } else {
+        final long bits;
+        if (field.numericValue() instanceof Integer || field.numericValue() instanceof Long) {
+          bits = field.numericValue().longValue();
+        } else if (field.numericValue() instanceof Float) {
+          bits = Float.floatToIntBits(field.numericValue().floatValue());
+        } else {
+          assert field.numericValue() instanceof Double;
+          bits = Double.doubleToLongBits(field.numericValue().doubleValue());
+        }
+        fields.add(new NumericDocValuesField(sf.getName(), bits));
+      }
+    }
+    if (sf.stored()) {
+      fields.add(getStoredField(sf, value));
+    }
+    return fields;
+  }
+
+  protected abstract StoredField getStoredField(SchemaField sf, Object value);
+
+  @Override
+  public void checkSchemaField(final SchemaField field) {
+    // PointFields support DocValues
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/schema/SchemaField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/SchemaField.java b/solr/core/src/java/org/apache/solr/schema/SchemaField.java
index bcd68c2..009e5fc 100644
--- a/solr/core/src/java/org/apache/solr/schema/SchemaField.java
+++ b/solr/core/src/java/org/apache/solr/schema/SchemaField.java
@@ -170,6 +170,11 @@ public final class SchemaField extends FieldProperties implements IndexableField
                               "can not sort on multivalued field: " 
                               + getName());
     }
+    if (this.type.isPointField() && !hasDocValues()) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, 
+                              "can not sort on a PointField without doc values: " 
+                              + getName());
+    }
   }
 
   /** 
@@ -191,6 +196,11 @@ public final class SchemaField extends FieldProperties implements IndexableField
                               "can not use FieldCache on multivalued field: " 
                               + getName());
     }
+    if (this.type.isPointField() && !hasDocValues()) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, 
+                              "Point fields can't use FieldCache. Use docValues=true for field: " 
+                              + getName());
+    }
     
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
index 6d13b51..7c56311 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
@@ -98,6 +98,7 @@ import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.schema.BoolField;
 import org.apache.solr.schema.EnumField;
 import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.PointField;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.TrieDateField;
 import org.apache.solr.schema.TrieDoubleField;
@@ -821,16 +822,39 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
               continue;
             }
             Object newVal = val;
-            if (schemaField.getType() instanceof TrieIntField) {
-              newVal = val.intValue();
-            } else if (schemaField.getType() instanceof TrieFloatField) {
-              newVal = Float.intBitsToFloat(val.intValue());
-            } else if (schemaField.getType() instanceof TrieDoubleField) {
-              newVal = Double.longBitsToDouble(val);
-            } else if (schemaField.getType() instanceof TrieDateField) {
-              newVal = new Date(val);
-            } else if (schemaField.getType() instanceof EnumField) {
-              newVal = ((EnumField) schemaField.getType()).intValueToStringValue(val.intValue());
+            if (schemaField.getType().isPointField()) {
+              PointField.PointTypes type = ((PointField)schemaField.getType()).getType(); 
+              switch (type) {
+                case INTEGER:
+                  newVal = val.intValue();
+                  break;
+                case LONG:
+                  newVal = val.longValue();
+                  break;
+                case FLOAT:
+                  newVal = Float.intBitsToFloat(val.intValue());
+                  break;
+                case DOUBLE:
+                  newVal = Double.longBitsToDouble(val);
+                  break;
+                case DATE:
+                  newVal = new Date(val);
+                  break;
+                default:
+                  throw new AssertionError("Unexpected PointType: " + type);
+              }
+            } else {
+              if (schemaField.getType() instanceof TrieIntField) {
+                newVal = val.intValue();
+              } else if (schemaField.getType() instanceof TrieFloatField) {
+                newVal = Float.intBitsToFloat(val.intValue());
+              } else if (schemaField.getType() instanceof TrieDoubleField) {
+                newVal = Double.longBitsToDouble(val);
+              } else if (schemaField.getType() instanceof TrieDateField) {
+                newVal = new Date(val);
+              } else if (schemaField.getType() instanceof EnumField) {
+                newVal = ((EnumField) schemaField.getType()).intValueToStringValue(val.intValue());
+              }
             }
             doc.addField(fieldName, newVal);
             break;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/search/TermQParserPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/TermQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/TermQParserPlugin.java
index 99ef4c4..89b3d28 100644
--- a/solr/core/src/java/org/apache/solr/search/TermQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/TermQParserPlugin.java
@@ -50,10 +50,16 @@ public class TermQParserPlugin extends QParserPlugin {
         String fname = localParams.get(QueryParsing.F);
         FieldType ft = req.getSchema().getFieldTypeNoEx(fname);
         String val = localParams.get(QueryParsing.V);
-        BytesRefBuilder term = new BytesRefBuilder();
+        BytesRefBuilder term;
         if (ft != null) {
-          ft.readableToIndexed(val, term);
+          if (ft.isPointField()) {
+            return ft.getFieldQuery(this, req.getSchema().getField(fname), val);
+          } else {
+            term = new BytesRefBuilder();
+            ft.readableToIndexed(val, term);
+          }
         } else {
+          term = new BytesRefBuilder();
           term.copyChars(val);
         }
         return new TermQuery(new Term(fname, term.get()));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/search/TermsQParserPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/TermsQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/TermsQParserPlugin.java
index 3a60149..c407353 100644
--- a/solr/core/src/java/org/apache/solr/search/TermsQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/TermsQParserPlugin.java
@@ -17,6 +17,7 @@
 package org.apache.solr.search;
 
 import java.util.Arrays;
+import java.util.Locale;
 import java.util.regex.Pattern;
 
 import org.apache.lucene.index.Term;
@@ -35,6 +36,7 @@ import org.apache.lucene.util.automaton.Automaton;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.PointField;
 
 /**
  * Finds documents whose specified field has any of the specified values. It's like
@@ -110,6 +112,14 @@ public class TermsQParserPlugin extends QParserPlugin {
           return new MatchNoDocsQuery();
         final String[] splitVals = sepIsSpace ? qstr.split("\\s+") : qstr.split(Pattern.quote(separator), -1);
         assert splitVals.length > 0;
+        
+        if (ft.isPointField()) {
+          if (localParams.get(METHOD) != null) {
+            throw new IllegalArgumentException(
+                String.format(Locale.ROOT, "Method '%s' not supported in TermsQParser when using PointFields", localParams.get(METHOD)));
+          }
+          return ((PointField)ft).getSetQuery(this, req.getSchema().getField(fname), Arrays.asList(splitVals));
+        }
 
         BytesRef[] bytesRefs = new BytesRef[splitVals.length];
         BytesRefBuilder term = new BytesRefBuilder();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java b/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
index 99f6fce..900bbf7 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
@@ -30,6 +30,7 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.FacetParams;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.PointField;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.TrieDateField;
 import org.apache.solr.schema.TrieField;
@@ -141,7 +142,32 @@ class FacetRangeProcessor extends FacetProcessor<FacetRange> {
               (SolrException.ErrorCode.BAD_REQUEST,
                   "Expected numeric field type :" + sf);
       }
-    } else {
+    } else if (ft instanceof PointField) {
+      final PointField pfield = (PointField)ft;
+
+      switch (pfield.getType()) {
+        case FLOAT:
+          calc = new FloatCalc(sf);
+          break;
+        case DOUBLE:
+          calc = new DoubleCalc(sf);
+          break;
+        case INTEGER:
+          calc = new IntCalc(sf);
+          break;
+        case LONG:
+          calc = new LongCalc(sf);
+          break;
+        case DATE:
+          calc = new DateCalc(sf, null);
+          break;
+        default:
+          throw new SolrException
+              (SolrException.ErrorCode.BAD_REQUEST,
+                  "Expected numeric field type :" + sf);
+      }
+    } 
+    else {
       throw new SolrException
           (SolrException.ErrorCode.BAD_REQUEST,
               "Expected numeric field type :" + sf);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/java/org/apache/solr/spelling/suggest/DocumentExpressionDictionaryFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/spelling/suggest/DocumentExpressionDictionaryFactory.java b/solr/core/src/java/org/apache/solr/spelling/suggest/DocumentExpressionDictionaryFactory.java
index b0d7007..3b7abdf 100644
--- a/solr/core/src/java/org/apache/solr/spelling/suggest/DocumentExpressionDictionaryFactory.java
+++ b/solr/core/src/java/org/apache/solr/spelling/suggest/DocumentExpressionDictionaryFactory.java
@@ -28,7 +28,11 @@ import org.apache.lucene.search.SortField;
 import org.apache.lucene.search.spell.Dictionary;
 import org.apache.lucene.search.suggest.DocumentValueSourceDictionary;
 import org.apache.solr.core.SolrCore;
+import org.apache.solr.schema.DoublePointField;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.FloatPointField;
+import org.apache.solr.schema.IntPointField;
+import org.apache.solr.schema.LongPointField;
 import org.apache.solr.schema.TrieDoubleField;
 import org.apache.solr.schema.TrieFloatField;
 import org.apache.solr.schema.TrieIntField;
@@ -111,13 +115,13 @@ public class DocumentExpressionDictionaryFactory extends DictionaryFactory {
     SortField.Type type = null;
     String fieldTypeName = core.getLatestSchema().getField(sortFieldName).getType().getTypeName();
     FieldType ft = core.getLatestSchema().getFieldTypes().get(fieldTypeName);
-    if (ft instanceof TrieFloatField) {
+    if (ft instanceof TrieFloatField || ft instanceof FloatPointField) {
       type = SortField.Type.FLOAT;
-    } else if (ft instanceof TrieIntField) {
+    } else if (ft instanceof TrieIntField || ft instanceof IntPointField) {
       type = SortField.Type.INT;
-    } else if (ft instanceof TrieLongField) {
+    } else if (ft instanceof TrieLongField || ft instanceof LongPointField) {
       type = SortField.Type.LONG;
-    } else if (ft instanceof TrieDoubleField) {
+    } else if (ft instanceof TrieDoubleField || ft instanceof DoublePointField) {
       type = SortField.Type.DOUBLE;
     }
     return type;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test-files/solr/collection1/conf/schema-distrib-interval-faceting.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema-distrib-interval-faceting.xml b/solr/core/src/test-files/solr/collection1/conf/schema-distrib-interval-faceting.xml
index 79d200d..ff73fdc 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema-distrib-interval-faceting.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema-distrib-interval-faceting.xml
@@ -24,12 +24,18 @@
   <fieldType name="date" class="solr.TrieDateField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
   <fieldType name="string" class="solr.StrField" sortMissingLast="true"/>
   <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
+  
+  <!-- Point Fields -->
+  <fieldType name="pint" class="solr.IntPointField" docValues="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+  <fieldType name="pdouble" class="solr.DoublePointField" docValues="true"/>
+  <fieldType name="pfloat" class="solr.FloatPointField" docValues="true"/>
 
   <field name="id" type="string" indexed="true" stored="true" docValues="false" multiValued="false" required="true"/>
   <field name="id_dv" type="string" indexed="false" stored="false" docValues="true" multiValued="false"
          required="true"/>
   <dynamicField name="*_i" type="int" indexed="true" stored="false" docValues="false"/>
-  <dynamicField name="*_i_dv" type="int" indexed="false" stored="false" docValues="true"/>
+  <dynamicField name="*_i_dv" type="${solr.tests.intClass:pint}" indexed="false" stored="false" docValues="true"/>
   <dynamicField name="*_is" type="int" indexed="true" stored="false" docValues="false" multiValued="true"/>
   <dynamicField name="*_is_dv" type="int" indexed="false" stored="false" docValues="true" multiValued="true"/>
   <dynamicField name="*_s" type="string" indexed="true" stored="false" docValues="false"/>
@@ -37,13 +43,13 @@
   <dynamicField name="*_ss" type="string" indexed="true" stored="false" docValues="false" multiValued="true"/>
   <dynamicField name="*_ss_dv" type="string" indexed="false" stored="false" docValues="true" multiValued="true"/>
   <dynamicField name="*_f" type="float" indexed="true" stored="false" docValues="false"/>
-  <dynamicField name="*_f_dv" type="float" indexed="true" stored="false" docValues="true"/>
+  <dynamicField name="*_f_dv" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="false" docValues="true"/>
   <dynamicField name="*_fs_dv" type="float" indexed="true" stored="false" docValues="true" multiValued="true"/>
   <dynamicField name="*_l" type="long" indexed="true" stored="false" docValues="false"/>
-  <dynamicField name="*_l_dv" type="long" indexed="true" stored="false" docValues="true"/>
+  <dynamicField name="*_l_dv" type="${solr.tests.longClass:plong}" indexed="true" stored="false" docValues="true"/>
   <dynamicField name="*_ls_dv" type="long" indexed="true" stored="false" docValues="true" multiValued="true"/>
   <dynamicField name="*_d" type="double" indexed="true" stored="false" docValues="false"/>
-  <dynamicField name="*_d_dv" type="double" indexed="true" stored="false" docValues="true"/>
+  <dynamicField name="*_d_dv" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="false" docValues="true"/>
   <dynamicField name="*_ds_dv" type="double" indexed="true" stored="false" docValues="true" multiValued="true"/>
   <dynamicField name="*_dt" type="date" indexed="true" stored="false" docValues="false"/>
   <dynamicField name="*_dt_dv" type="date" indexed="true" stored="false" docValues="true"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test-files/solr/collection1/conf/schema-docValuesFaceting.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema-docValuesFaceting.xml b/solr/core/src/test-files/solr/collection1/conf/schema-docValuesFaceting.xml
index 113e868..673e7dd 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema-docValuesFaceting.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema-docValuesFaceting.xml
@@ -23,12 +23,17 @@
   <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
   <fieldType name="date" class="solr.TrieDateField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
   <fieldType name="string" class="solr.StrField" sortMissingLast="true"/>
+  <fieldType name="pint" class="solr.IntPointField"/>
+  <fieldType name="plong" class="solr.LongPointField"/>
+  <fieldType name="pdouble" class="solr.DoublePointField"/>
+  <fieldType name="pfloat" class="solr.FloatPointField"/>
 
   <field name="id" type="string" indexed="true" stored="true" docValues="false" multiValued="false" required="true"/>
   <field name="id_dv" type="string" indexed="false" stored="false" docValues="true" multiValued="false"
          required="true"/>
   <dynamicField name="*_i" type="int" indexed="true" stored="false" docValues="false"/>
   <dynamicField name="*_i_dv" type="int" indexed="false" stored="false" docValues="true"/>
+  <dynamicField name="*_i_p" type="pint" indexed="true" stored="false" docValues="true"/>
   <dynamicField name="*_is" type="int" indexed="true" stored="false" docValues="false" multiValued="true"/>
   <dynamicField name="*_is_dv" type="int" indexed="false" stored="false" docValues="true" multiValued="true"/>
   <dynamicField name="*_s" type="string" indexed="true" stored="false" docValues="false"/>
@@ -37,14 +42,17 @@
   <dynamicField name="*_ss_dv" type="string" indexed="false" stored="false" docValues="true" multiValued="true"/>
   <dynamicField name="*_f" type="float" indexed="true" stored="false" docValues="false"/>
   <dynamicField name="*_f_dv" type="float" indexed="true" stored="false" docValues="true"/>
+  <dynamicField name="*_f_p" type="pfloat" indexed="true" stored="false" docValues="true"/>
   <dynamicField name="*_fs" type="float" indexed="true" stored="false" docValues="false" multiValued="true"/>
   <dynamicField name="*_fs_dv" type="float" indexed="true" stored="false" docValues="true" multiValued="true"/>
   <dynamicField name="*_l" type="long" indexed="true" stored="false" docValues="false"/>
   <dynamicField name="*_l_dv" type="long" indexed="true" stored="false" docValues="true"/>
+  <dynamicField name="*_l_p" type="plong" indexed="true" stored="false" docValues="true"/>
   <dynamicField name="*_ls" type="long" indexed="true" stored="false" docValues="false" multiValued="true"/>
   <dynamicField name="*_ls_dv" type="long" indexed="true" stored="false" docValues="true" multiValued="true"/>
   <dynamicField name="*_d" type="double" indexed="true" stored="false" docValues="false"/>
   <dynamicField name="*_d_dv" type="double" indexed="true" stored="false" docValues="true"/>
+  <dynamicField name="*_d_p" type="pdouble" indexed="true" stored="false" docValues="true"/>
   <dynamicField name="*_ds" type="double" indexed="true" stored="false" docValues="false" multiValued="true"/>
   <dynamicField name="*_ds_dv" type="double" indexed="true" stored="false" docValues="true" multiValued="true"/>
   <dynamicField name="*_dt" type="date" indexed="true" stored="false" docValues="false"/>
@@ -55,11 +63,15 @@
   <uniqueKey>id</uniqueKey>
 
   <copyField source="*_i" dest="*_i_dv"/>
+  <copyField source="*_i" dest="*_i_p"/>
   <copyField source="*_f" dest="*_f_dv"/>
+  <copyField source="*_f" dest="*_f_p"/>
   <copyField source="*_is" dest="*_is_dv"/>
   <copyField source="*_s" dest="*_s_dv"/>
   <copyField source="*_l" dest="*_l_dv"/>
+  <copyField source="*_l" dest="*_l_p"/>
   <copyField source="*_d" dest="*_d_dv"/>
+  <copyField source="*_d" dest="*_d_p"/>
   <copyField source="*_ss" dest="*_ss_dv"/>
   <copyField source="*_is" dest="*_is_dv"/>
   <copyField source="*_fs" dest="*_fs_dv"/>


[17/38] lucene-solr:jira/solr-9857: SOLR-9977: Fix config bug in DistribDocExpirationUpdateProcessorTest that allowed false assumptions about when index version changes

Posted by ab...@apache.org.
SOLR-9977: Fix config bug in DistribDocExpirationUpdateProcessorTest that allowed false assumptions about when index version changes


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

Branch: refs/heads/jira/solr-9857
Commit: 9ee48aa857e15461dd6ec6482194141da72e0ba2
Parents: 0bdcfc2
Author: Chris Hostetter <ho...@apache.org>
Authored: Tue Jan 17 17:32:42 2017 -0700
Committer: Chris Hostetter <ho...@apache.org>
Committed: Tue Jan 17 17:32:42 2017 -0700

----------------------------------------------------------------------
 solr/CHANGES.txt                                |  3 ++
 .../solrconfig.snippet.randomindexconfig.xml    | 47 --------------------
 .../configsets/doc-expiry/conf/solrconfig.xml   | 15 ++++++-
 3 files changed, 16 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9ee48aa8/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 42be8a2..5fd8a9e 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -95,6 +95,9 @@ Bug Fixes
 
 * SOLR-9976: Fix init bug in SegmentsInfoRequestHandlerTest (hossman)
 
+* SOLR-9977: Fix config bug in DistribDocExpirationUpdateProcessorTest that allowed false assumptions
+  about when index version changes (hossman)
+
 Optimizations
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9ee48aa8/solr/core/src/test-files/solr/configsets/doc-expiry/conf/solrconfig.snippet.randomindexconfig.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/configsets/doc-expiry/conf/solrconfig.snippet.randomindexconfig.xml b/solr/core/src/test-files/solr/configsets/doc-expiry/conf/solrconfig.snippet.randomindexconfig.xml
deleted file mode 100644
index ec5f54e..0000000
--- a/solr/core/src/test-files/solr/configsets/doc-expiry/conf/solrconfig.snippet.randomindexconfig.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" ?>
-
-<!--
- 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.
--->
-<!-- 
-
-A solrconfig.xml snippet containing indexConfig settings for randomized testing.
-
--->
-<indexConfig>
-  <!-- this sys property is not set by SolrTestCaseJ4 because we ideally want to use
-       the RandomMergePolicy in all tests - but some tests expect very specific
-       Merge behavior, so those tests can set it as needed.
-  -->
-  <mergePolicy enable="${solr.tests.useMergePolicy:true}" class="${solr.tests.mergePolicy:org.apache.solr.util.RandomMergePolicy}" />
-  <mergePolicyFactory enable="${solr.tests.useMergePolicyFactory:true}" class="${solr.tests.mergePolicyFactory:org.apache.solr.util.RandomMergePolicyFactory}" />
-  
-  <useCompoundFile>${useCompoundFile:false}</useCompoundFile>
-
-  <maxBufferedDocs>${solr.tests.maxBufferedDocs}</maxBufferedDocs>
-  <ramBufferSizeMB>${solr.tests.ramBufferSizeMB}</ramBufferSizeMB>
-
-  <mergeScheduler class="${solr.tests.mergeScheduler}" />
-
-  <writeLockTimeout>1000</writeLockTimeout>
-  <commitLockTimeout>10000</commitLockTimeout>
-
-  <!-- this sys property is not set by SolrTestCaseJ4 because almost all tests should
-       use the single process lockType for speed - but tests that explicitly need
-       to vary the lockType canset it as needed.
-  -->
-  <lockType>${solr.tests.lockType:single}</lockType>
-</indexConfig>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9ee48aa8/solr/core/src/test-files/solr/configsets/doc-expiry/conf/solrconfig.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/configsets/doc-expiry/conf/solrconfig.xml b/solr/core/src/test-files/solr/configsets/doc-expiry/conf/solrconfig.xml
index 18d16a3..2599744 100644
--- a/solr/core/src/test-files/solr/configsets/doc-expiry/conf/solrconfig.xml
+++ b/solr/core/src/test-files/solr/configsets/doc-expiry/conf/solrconfig.xml
@@ -25,14 +25,25 @@
 
   <dataDir>${solr.data.dir:}</dataDir>
 
+  <indexConfig>
+    <!-- NOTE: we do *NOT* want Randomized Merging for these tests,
+         because we need to be able to assert that index changes are only happening
+         on the shards we expected them to as a result of our deletes.
+         
+         (the random/mock merge classes can cause new readers to be opened after a commit
+         even if the index itself hasn't changed - ex: new segments file listing same exact segments
+         
+         Instead use Solr defaults for almost everything
+    -->
+    <lockType>${solr.tests.lockType:single}</lockType>
+  </indexConfig>
+  
   <directoryFactory name="DirectoryFactory"
                     class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
   <schemaFactory class="ClassicIndexSchemaFactory"/>
 
   <luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
 
-  <xi:include href="solrconfig.snippet.randomindexconfig.xml" xmlns:xi="http://www.w3.org/2001/XInclude"/>
-
   <updateHandler class="solr.DirectUpdateHandler2">
     <updateLog>
       <str name="dir">${solr.ulog.dir:}</str>


[05/38] lucene-solr:jira/solr-9857: SOLR-9941: Moving changelog entry from 7.0.0 to 6.5.0

Posted by ab...@apache.org.
SOLR-9941: Moving changelog entry from 7.0.0 to 6.5.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/38af094d
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/38af094d
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/38af094d

Branch: refs/heads/jira/solr-9857
Commit: 38af094d175daebe4093782cc06e964cfc2dd14b
Parents: 205f9cc
Author: Ishan Chattopadhyaya <is...@apache.org>
Authored: Tue Jan 17 03:12:07 2017 +0530
Committer: Ishan Chattopadhyaya <is...@apache.org>
Committed: Tue Jan 17 03:12:07 2017 +0530

----------------------------------------------------------------------
 solr/CHANGES.txt | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/38af094d/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 4874067..5b96c20 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -74,9 +74,6 @@ Optimizations
 * SOLR-9584: Support Solr being proxied with another endpoint than default /solr, by using relative links
   in AdminUI javascripts (Yun Jie Zhou via janhoy)
 
-* SOLR-9941: Clear the deletes lists at UpdateLog before replaying from log. This prevents redundantly pre-applying
-  DBQs, during the log replay, to every update in the log as if the DBQs were out of order. (hossman, Ishan Chattopadhyaya)
-
 ==================  6.5.0 ==================
 
 Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.
@@ -90,9 +87,14 @@ Apache UIMA 2.3.1
 Apache ZooKeeper 3.4.6
 Jetty 9.3.14.v20161028
 
+Detailed Change List
+----------------------
 
-(No Changes)
+Optimizations
+----------------------
 
+* SOLR-9941: Clear the deletes lists at UpdateLog before replaying from log. This prevents redundantly pre-applying
+  DBQs, during the log replay, to every update in the log as if the DBQs were out of order. (hossman, Ishan Chattopadhyaya)
 
 ==================  6.4.0 ==================
 


[38/38] lucene-solr:jira/solr-9857: Merge branch 'master' into jira/solr-9857

Posted by ab...@apache.org.
Merge branch 'master' into jira/solr-9857


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

Branch: refs/heads/jira/solr-9857
Commit: aef1f73a3fae05ef09f3e421acc3e6d5dc378bbf
Parents: 197bf00 a14d793
Author: Andrzej Bialecki <ab...@apache.org>
Authored: Thu Jan 19 16:31:57 2017 +0100
Committer: Andrzej Bialecki <ab...@apache.org>
Committed: Thu Jan 19 16:31:57 2017 +0100

----------------------------------------------------------------------
 .../dot.settings/org.eclipse.jdt.core.prefs     |    8 +-
 lucene/CHANGES.txt                              |   32 +-
 .../analysis/charfilter/BaseCharFilter.java     |   26 +-
 .../analysis/core/FlattenGraphFilter.java       |  418 +++++
 .../core/FlattenGraphFilterFactory.java         |   44 +
 .../miscellaneous/WordDelimiterFilter.java      |    9 +-
 .../WordDelimiterFilterFactory.java             |    6 +
 .../miscellaneous/WordDelimiterGraphFilter.java |  706 +++++++++
 .../WordDelimiterGraphFilterFactory.java        |  199 +++
 .../miscellaneous/WordDelimiterIterator.java    |   59 +-
 .../analysis/synonym/FlattenGraphFilter.java    |  417 -----
 .../synonym/FlattenGraphFilterFactory.java      |   44 -
 .../lucene/analysis/synonym/SynonymFilter.java  |    1 +
 .../analysis/synonym/SynonymFilterFactory.java  |    1 +
 .../analysis/synonym/SynonymGraphFilter.java    |   11 +-
 ...ache.lucene.analysis.util.TokenFilterFactory |    3 +-
 .../analysis/core/TestFlattenGraphFilter.java   |  284 ++++
 .../lucene/analysis/core/TestRandomChains.java  |    3 +
 .../miscellaneous/TestWordDelimiterFilter.java  |   69 +
 .../TestWordDelimiterGraphFilter.java           |  897 +++++++++++
 .../synonym/TestFlattenGraphFilter.java         |  284 ----
 .../synonym/TestSynonymGraphFilter.java         |   51 +-
 .../codecs/simpletext/SimpleTextBKDReader.java  |   50 +
 lucene/common-build.xml                         |    1 +
 .../lucene/analysis/TokenStreamToAutomaton.java |   39 +-
 .../tokenattributes/OffsetAttributeImpl.java    |    2 +-
 .../PackedTokenAttributeImpl.java               |    2 +-
 .../PositionIncrementAttributeImpl.java         |    3 +-
 .../PositionLengthAttributeImpl.java            |    3 +-
 .../org/apache/lucene/codecs/PointsWriter.java  |    5 +
 .../org/apache/lucene/index/CheckIndex.java     |   37 +
 .../lucene/index/DefaultIndexingChain.java      |    5 +-
 .../org/apache/lucene/index/IndexWriter.java    |   10 +-
 .../org/apache/lucene/index/PointValues.java    |    7 +
 .../apache/lucene/index/PointValuesWriter.java  |   10 +
 .../apache/lucene/index/SortingLeafReader.java  |    7 +-
 .../lucene/search/Boolean2ScorerSupplier.java   |  217 +++
 .../org/apache/lucene/search/BooleanWeight.java |  136 +-
 .../apache/lucene/search/ConjunctionDISI.java   |    2 +-
 .../apache/lucene/search/ConjunctionScorer.java |    3 +-
 .../lucene/search/ConstantScoreQuery.java       |   46 +-
 .../lucene/search/DoubleValuesSource.java       |    2 +-
 .../lucene/search/FieldComparatorSource.java    |    8 +-
 .../lucene/search/FieldValueHitQueue.java       |   11 +-
 .../apache/lucene/search/LongValuesSource.java  |    2 +-
 .../lucene/search/MinShouldMatchSumScorer.java  |   22 +-
 .../apache/lucene/search/PointRangeQuery.java   |  214 ++-
 .../apache/lucene/search/ScorerSupplier.java    |   47 +
 .../org/apache/lucene/search/SortField.java     |    2 +-
 .../lucene/search/SortedNumericSortField.java   |    2 +-
 .../lucene/search/SortedSetSortField.java       |    2 +-
 .../apache/lucene/search/TermInSetQuery.java    |   93 +-
 .../java/org/apache/lucene/search/TopDocs.java  |   14 +-
 .../apache/lucene/search/TopFieldCollector.java |    4 +-
 .../java/org/apache/lucene/search/Weight.java   |   25 +
 .../org/apache/lucene/util/bkd/BKDReader.java   |   96 ++
 .../org/apache/lucene/util/bkd/BKDWriter.java   |   14 +-
 .../lucene/analysis/TestGraphTokenizers.java    |   53 +-
 .../lucene60/TestLucene60PointsFormat.java      |  200 ++-
 .../lucene/search/TermInSetQueryTest.java       |  123 +-
 .../search/TestBoolean2ScorerSupplier.java      |  332 ++++
 .../search/TestBooleanQueryVisitSubscorers.java |    4 +-
 .../lucene/search/TestElevationComparator.java  |   22 +-
 .../apache/lucene/search/TestFilterWeight.java  |    3 +-
 .../apache/lucene/search/TestPointQueries.java  |   35 +
 .../apache/lucene/util/TestDocIdSetBuilder.java |    5 +
 .../org/apache/lucene/util/bkd/TestBKD.java     |   90 ++
 .../util/bkd/TestMutablePointsReaderUtils.java  |    5 +
 .../apache/lucene/facet/MultiFacetQuery.java    |   13 +-
 .../search/grouping/BlockGroupingCollector.java |    2 +-
 .../grouping/FirstPassGroupingCollector.java    |    3 +-
 .../lucene/search/grouping/SearchGroup.java     |    8 +-
 .../lucene/search/grouping/TopGroups.java       |    5 +-
 .../search/join/ToParentBlockJoinCollector.java |    2 +-
 .../search/join/ToParentBlockJoinSortField.java |    2 +-
 .../apache/lucene/index/memory/MemoryIndex.java |    5 +
 .../lucene/queries/function/ValueSource.java    |    2 +-
 .../lucene/queryparser/xml/CoreParser.java      |    2 -
 .../lucene/document/LatLonPointSortField.java   |    6 +-
 .../lucene/search/DocValuesRangeQuery.java      |   11 +-
 .../lucene/search/IndexOrDocValuesQuery.java    |  116 ++
 .../search/TestIndexOrDocValuesQuery.java       |   89 ++
 lucene/site/changes/changes2html.pl             |    3 +-
 .../spatial/prefix/NumberRangeFacetsTest.java   |    8 +-
 .../spatial3d/Geo3DPointOutsideSortField.java   |    5 +-
 .../lucene/spatial3d/Geo3DPointSortField.java   |    5 +-
 .../suggest/analyzing/AnalyzingSuggester.java   |    3 +-
 .../analysis/BaseTokenStreamTestCase.java       |  114 +-
 .../lucene/analysis/TokenStreamToDot.java       |    5 +-
 .../asserting/AssertingLiveDocsFormat.java      |    9 +-
 .../codecs/cranky/CrankyPointsFormat.java       |    5 +
 .../lucene/index/AssertingLeafReader.java       |    7 +
 .../apache/lucene/search/AssertingWeight.java   |   42 +-
 solr/CHANGES.txt                                |   52 +-
 solr/bin/solr                                   |   11 +-
 solr/bin/solr.cmd                               |   13 +-
 solr/bin/solr.in.cmd                            |    5 +
 solr/bin/solr.in.sh                             |    5 +
 .../org/apache/solr/core/CoreContainer.java     |  109 +-
 .../org/apache/solr/core/DirectoryFactory.java  |   27 +
 .../src/java/org/apache/solr/core/SolrCore.java |  120 +-
 .../org/apache/solr/handler/IndexFetcher.java   |   60 +-
 .../org/apache/solr/handler/RestoreCore.java    |    2 +-
 .../solr/handler/admin/CoreAdminOperation.java  |    1 +
 .../solr/handler/admin/LukeRequestHandler.java  |    7 +-
 .../solr/handler/component/FacetComponent.java  |   12 +-
 .../solr/handler/component/QueryComponent.java  |   25 +-
 .../component/QueryElevationComponent.java      |   60 +-
 .../handler/component/RangeFacetProcessor.java  |    3 +-
 .../handler/component/RangeFacetRequest.java    |   31 +-
 .../component/ShardFieldSortedHitQueue.java     |    8 +-
 .../solr/handler/component/StatsComponent.java  |    6 +
 .../handler/component/StatsValuesFactory.java   |    2 +-
 .../solr/highlight/UnifiedSolrHighlighter.java  |   10 +-
 .../solr/index/SlowCompositeReaderWrapper.java  |    3 -
 .../org/apache/solr/request/IntervalFacets.java |    4 +
 .../org/apache/solr/request/SimpleFacets.java   |   37 +-
 .../apache/solr/request/json/RequestUtil.java   |   16 +-
 .../org/apache/solr/response/DocsStreamer.java  |    8 +
 .../apache/solr/schema/DoublePointField.java    |  187 +++
 .../java/org/apache/solr/schema/FieldType.java  |    9 +-
 .../org/apache/solr/schema/FloatPointField.java |  187 +++
 .../org/apache/solr/schema/IntPointField.java   |  186 +++
 .../org/apache/solr/schema/LongPointField.java  |  186 +++
 .../java/org/apache/solr/schema/PointField.java |  233 +++
 .../org/apache/solr/schema/SchemaField.java     |   10 +
 .../solr/search/CollapsingQParserPlugin.java    |    2 +-
 .../apache/solr/search/SolrIndexSearcher.java   |   44 +-
 .../apache/solr/search/TermQParserPlugin.java   |   10 +-
 .../apache/solr/search/TermsQParserPlugin.java  |   10 +
 .../apache/solr/search/facet/FacetRange.java    |   28 +-
 .../SearchGroupShardResponseProcessor.java      |  160 +-
 .../TopGroupsShardResponseProcessor.java        |  114 +-
 .../solr/security/GenericHadoopAuthPlugin.java  |  266 ----
 .../apache/solr/security/HadoopAuthPlugin.java  |    2 +-
 .../DocumentExpressionDictionaryFactory.java    |   12 +-
 .../conf/schema-distrib-interval-faceting.xml   |   14 +-
 .../conf/schema-docValuesFaceting.xml           |   12 +
 .../solr/collection1/conf/schema-point.xml      |   88 ++
 .../solr/collection1/conf/schema-sorts.xml      |   44 +-
 .../test-files/solr/collection1/conf/schema.xml |   26 +-
 .../solr/collection1/conf/schema11.xml          |   19 +-
 .../solr/collection1/conf/schema12.xml          |   15 +-
 .../solr/collection1/conf/schema_latest.xml     |   21 +-
 .../solrconfig.snippet.randomindexconfig.xml    |   47 -
 .../configsets/doc-expiry/conf/solrconfig.xml   |   15 +-
 .../apache/solr/TestDistributedGrouping.java    |   10 +-
 .../org/apache/solr/TestDistributedSearch.java  |   46 +-
 .../core/src/test/org/apache/solr/TestJoin.java |    6 +-
 .../org/apache/solr/TestRandomDVFaceting.java   |    8 +
 .../org/apache/solr/TestRandomFaceting.java     |   12 +-
 .../solr/cloud/MissingSegmentRecoveryTest.java  |  123 ++
 .../apache/solr/cloud/TestCloudPivotFacet.java  |    2 +
 .../handler/XsltUpdateRequestHandlerTest.java   |    2 +-
 .../handler/admin/LukeRequestHandlerTest.java   |    8 +-
 .../admin/SegmentsInfoRequestHandlerTest.java   |   20 +-
 .../component/SpellCheckComponentTest.java      |   36 +
 .../handler/component/TestExpandComponent.java  |    8 +-
 .../highlight/TestUnifiedSolrHighlighter.java   |    7 +-
 .../apache/solr/request/TestFacetMethods.java   |   12 +
 .../apache/solr/schema/SortableBinaryField.java |    3 +-
 .../org/apache/solr/schema/TestPointFields.java | 1472 ++++++++++++++++++
 .../ApacheLuceneSolrNearQueryBuilder.java       |    1 -
 .../apache/solr/search/GoodbyeQueryBuilder.java |    1 -
 .../apache/solr/search/HandyQueryBuilder.java   |    1 -
 .../apache/solr/search/HelloQueryBuilder.java   |    1 -
 .../solr/search/TestCollapseQParserPlugin.java  |    4 +-
 .../solr/search/TestMaxScoreQueryParser.java    |    2 +-
 .../search/TestRandomCollapseQParserPlugin.java |    2 +
 .../apache/solr/search/TestSolrQueryParser.java |   23 +
 .../solr/search/facet/TestJsonFacets.java       |    2 +
 solr/server/scripts/cloud-scripts/zkcli.bat     |    2 +-
 solr/server/scripts/cloud-scripts/zkcli.sh      |    2 +-
 .../java/org/apache/solr/SolrTestCaseJ4.java    |   44 +-
 .../solr/cloud/AbstractDistribZkTestBase.java   |    5 +-
 175 files changed, 8460 insertions(+), 2048 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/aef1f73a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
----------------------------------------------------------------------
diff --cc solr/core/src/java/org/apache/solr/core/CoreContainer.java
index c80816b,023e7b1..07a33ea
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@@ -167,8 -176,8 +177,10 @@@ public class CoreContainer 
  
    protected MetricsHandler metricsHandler;
  
 +  protected MetricsCollectorHandler metricsCollectorHandler;
 +
+   private enum CoreInitFailedAction { fromleader, none }
+ 
    /**
     * This method instantiates a new instance of {@linkplain BackupRepository}.
     *


[06/38] lucene-solr:jira/solr-9857: LUCENE-7637: Require that all terms of a TermsQuery come from the same field.

Posted by ab...@apache.org.
LUCENE-7637: Require that all terms of a TermsQuery come from the same field.


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

Branch: refs/heads/jira/solr-9857
Commit: 43874fc5b5c7fe37c70524693ea2db4ef0e01f95
Parents: 86233cb
Author: Adrien Grand <jp...@gmail.com>
Authored: Tue Jan 17 08:45:28 2017 +0100
Committer: Adrien Grand <jp...@gmail.com>
Committed: Tue Jan 17 08:51:58 2017 +0100

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |  11 +-
 .../apache/lucene/search/TermInSetQuery.java    |  93 ++++----------
 .../lucene/search/TermInSetQueryTest.java       | 123 +++++++------------
 .../apache/lucene/facet/MultiFacetQuery.java    |  13 +-
 .../spatial/prefix/NumberRangeFacetsTest.java   |   8 +-
 5 files changed, 84 insertions(+), 164 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/43874fc5/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 59992ea..2e015a3 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -63,6 +63,14 @@ Other
 
 ======================= Lucene 6.5.0 =======================
 
+API Changes
+
+* LUCENE-7624: TermsQuery has been renamed as TermInSetQuery and moved to core.
+  (Alan Woodward)
+
+* LUCENE-7637: TermInSetQuery requires that all terms come from the same field.
+  (Adrien Grand)
+
 New Features
 
 * LUCENE-7623: Add FunctionScoreQuery and FunctionMatchQuery (Alan Woodward,
@@ -107,9 +115,6 @@ API Changes
 * LUCENE-7611: DocumentValueSourceDictionary now takes a LongValuesSource
   as a parameter, and the ValueSource equivalent is deprecated (Alan Woodward)
 
-* LUCENE-7624: TermsQuery has been renamed as TermInSetQuery and moved to core.
-  (Alan Woodward)
-
 New features
 
 * LUCENE-5867: Added BooleanSimilarity. (Robert Muir, Adrien Grand)

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/43874fc5/lucene/core/src/java/org/apache/lucene/search/TermInSetQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/TermInSetQuery.java b/lucene/core/src/java/org/apache/lucene/search/TermInSetQuery.java
index e1a1575..08fe3c3 100644
--- a/lucene/core/src/java/org/apache/lucene/search/TermInSetQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/TermInSetQuery.java
@@ -21,7 +21,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -73,39 +72,12 @@ public class TermInSetQuery extends Query implements Accountable {
   // Same threshold as MultiTermQueryConstantScoreWrapper
   static final int BOOLEAN_REWRITE_TERM_COUNT_THRESHOLD = 16;
 
-  private final boolean singleField; // whether all terms are from the same field
+  private final String field;
   private final PrefixCodedTerms termData;
   private final int termDataHashCode; // cached hashcode of termData
 
   /**
-   * Creates a new {@link TermInSetQuery} from the given collection. It
-   * can contain duplicate terms and multiple fields.
-   */
-  public TermInSetQuery(Collection<Term> terms) {
-    Term[] sortedTerms = terms.toArray(new Term[terms.size()]);
-    // already sorted if we are a SortedSet with natural order
-    boolean sorted = terms instanceof SortedSet && ((SortedSet<Term>)terms).comparator() == null;
-    if (!sorted) {
-      ArrayUtil.timSort(sortedTerms);
-    }
-    PrefixCodedTerms.Builder builder = new PrefixCodedTerms.Builder();
-    Set<String> fields = new HashSet<>();
-    Term previous = null;
-    for (Term term : sortedTerms) {
-      if (term.equals(previous) == false) {
-        fields.add(term.field());
-        builder.add(term);
-      }
-      previous = term;
-    }
-    singleField = fields.size() == 1;
-    termData = builder.finish();
-    termDataHashCode = termData.hashCode();
-  }
-
-  /**
-   * Creates a new {@link TermInSetQuery} from the given collection for
-   * a single field. It can contain duplicate terms.
+   * Creates a new {@link TermInSetQuery} from the given collection of terms.
    */
   public TermInSetQuery(String field, Collection<BytesRef> terms) {
     BytesRef[] sortedTerms = terms.toArray(new BytesRef[terms.size()]);
@@ -125,27 +97,18 @@ public class TermInSetQuery extends Query implements Accountable {
       builder.add(field, term);
       previous.copyBytes(term);
     }
-    singleField = true;
+    this.field = field;
     termData = builder.finish();
     termDataHashCode = termData.hashCode();
   }
 
   /**
-   * Creates a new {@link TermInSetQuery} from the given {@link BytesRef} array for
-   * a single field.
+   * Creates a new {@link TermInSetQuery} from the given array of terms.
    */
   public TermInSetQuery(String field, BytesRef...terms) {
     this(field, Arrays.asList(terms));
   }
 
-  /**
-   * Creates a new {@link TermInSetQuery} from the given array. The array can
-   * contain duplicate terms and multiple fields.
-   */
-  public TermInSetQuery(final Term... terms) {
-    this(Arrays.asList(terms));
-  }
-
   @Override
   public Query rewrite(IndexReader reader) throws IOException {
     final int threshold = Math.min(BOOLEAN_REWRITE_TERM_COUNT_THRESHOLD, BooleanQuery.getMaxClauseCount());
@@ -167,6 +130,7 @@ public class TermInSetQuery extends Query implements Accountable {
   }
 
   private boolean equalsTo(TermInSetQuery other) {
+    // no need to check 'field' explicitly since it is encoded in 'termData'
     // termData might be heavy to compare so check the hash code first
     return termDataHashCode == other.termDataHashCode &&
         termData.equals(other.termData);
@@ -260,6 +224,15 @@ public class TermInSetQuery extends Query implements Accountable {
       private WeightOrDocIdSet rewrite(LeafReaderContext context) throws IOException {
         final LeafReader reader = context.reader();
 
+        final Fields fields = reader.fields();
+        Terms terms = fields.terms(field);
+        if (terms == null) {
+          return null;
+        }
+        TermsEnum termsEnum = terms.iterator();
+        PostingsEnum docs = null;
+        TermIterator iterator = termData.iterator();
+
         // We will first try to collect up to 'threshold' terms into 'matchingTerms'
         // if there are two many terms, we will fall back to building the 'builder'
         final int threshold = Math.min(BOOLEAN_REWRITE_TERM_COUNT_THRESHOLD, BooleanQuery.getMaxClauseCount());
@@ -267,25 +240,9 @@ public class TermInSetQuery extends Query implements Accountable {
         List<TermAndState> matchingTerms = new ArrayList<>(threshold);
         DocIdSetBuilder builder = null;
 
-        final Fields fields = reader.fields();
-        String lastField = null;
-        Terms terms = null;
-        TermsEnum termsEnum = null;
-        PostingsEnum docs = null;
-        TermIterator iterator = termData.iterator();
         for (BytesRef term = iterator.next(); term != null; term = iterator.next()) {
-          String field = iterator.field();
-          // comparing references is fine here
-          if (field != lastField) {
-            terms = fields.terms(field);
-            if (terms == null) {
-              termsEnum = null;
-            } else {
-              termsEnum = terms.iterator();
-            }
-            lastField = field;
-          }
-          if (termsEnum != null && termsEnum.seekExact(term)) {
+          assert field.equals(iterator.field());
+          if (termsEnum.seekExact(term)) {
             if (matchingTerms == null) {
               docs = termsEnum.postings(docs, PostingsEnum.NONE);
               builder.add(docs);
@@ -293,15 +250,7 @@ public class TermInSetQuery extends Query implements Accountable {
               matchingTerms.add(new TermAndState(field, termsEnum));
             } else {
               assert matchingTerms.size() == threshold;
-              if (singleField) {
-                // common case: all terms are in the same field
-                // use an optimized builder that leverages terms stats to be more efficient
-                builder = new DocIdSetBuilder(reader.maxDoc(), terms);
-              } else {
-                // corner case: different fields
-                // don't make assumptions about the docs we will get
-                builder = new DocIdSetBuilder(reader.maxDoc());
-              }
+              builder = new DocIdSetBuilder(reader.maxDoc(), terms);
               docs = termsEnum.postings(docs, PostingsEnum.NONE);
               builder.add(docs);
               for (TermAndState t : matchingTerms) {
@@ -344,7 +293,9 @@ public class TermInSetQuery extends Query implements Accountable {
       @Override
       public BulkScorer bulkScorer(LeafReaderContext context) throws IOException {
         final WeightOrDocIdSet weightOrBitSet = rewrite(context);
-        if (weightOrBitSet.weight != null) {
+        if (weightOrBitSet == null) {
+          return null;
+        } else if (weightOrBitSet.weight != null) {
           return weightOrBitSet.weight.bulkScorer(context);
         } else {
           final Scorer scorer = scorer(weightOrBitSet.set);
@@ -358,7 +309,9 @@ public class TermInSetQuery extends Query implements Accountable {
       @Override
       public Scorer scorer(LeafReaderContext context) throws IOException {
         final WeightOrDocIdSet weightOrBitSet = rewrite(context);
-        if (weightOrBitSet.weight != null) {
+        if (weightOrBitSet == null) {
+          return null;
+        } else if (weightOrBitSet.weight != null) {
           return weightOrBitSet.weight.scorer(context);
         } else {
           return scorer(weightOrBitSet.set);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/43874fc5/lucene/core/src/test/org/apache/lucene/search/TermInSetQueryTest.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/search/TermInSetQueryTest.java b/lucene/core/src/test/org/apache/lucene/search/TermInSetQueryTest.java
index e694d97..3878d59 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TermInSetQueryTest.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TermInSetQueryTest.java
@@ -18,15 +18,12 @@ package org.apache.lucene.search;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import com.carrotsearch.randomizedtesting.generators.RandomPicks;
 import com.carrotsearch.randomizedtesting.generators.RandomStrings;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field.Store;
@@ -53,25 +50,25 @@ public class TermInSetQueryTest extends LuceneTestCase {
 
   public void testDuel() throws IOException {
     final int iters = atLeast(2);
+    final String field = "f";
     for (int iter = 0; iter < iters; ++iter) {
-      final List<Term> allTerms = new ArrayList<>();
+      final List<BytesRef> allTerms = new ArrayList<>();
       final int numTerms = TestUtil.nextInt(random(), 1, 1 << TestUtil.nextInt(random(), 1, 10));
       for (int i = 0; i < numTerms; ++i) {
-        final String field = usually() ? "f" : "g";
         final String value = TestUtil.randomAnalysisString(random(), 10, true);
-        allTerms.add(new Term(field, value));
+        allTerms.add(new BytesRef(value));
       }
       Directory dir = newDirectory();
       RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
       final int numDocs = atLeast(100);
       for (int i = 0; i < numDocs; ++i) {
         Document doc = new Document();
-        final Term term = allTerms.get(random().nextInt(allTerms.size()));
-        doc.add(new StringField(term.field(), term.text(), Store.NO));
+        final BytesRef term = allTerms.get(random().nextInt(allTerms.size()));
+        doc.add(new StringField(field, term, Store.NO));
         iw.addDocument(doc);
       }
       if (numTerms > 1 && random().nextBoolean()) {
-        iw.deleteDocuments(new TermQuery(allTerms.get(0)));
+        iw.deleteDocuments(new TermQuery(new Term(field, allTerms.get(0))));
       }
       iw.commit();
       final IndexReader reader = iw.getReader();
@@ -87,16 +84,16 @@ public class TermInSetQueryTest extends LuceneTestCase {
       for (int i = 0; i < 100; ++i) {
         final float boost = random().nextFloat() * 10;
         final int numQueryTerms = TestUtil.nextInt(random(), 1, 1 << TestUtil.nextInt(random(), 1, 8));
-        List<Term> queryTerms = new ArrayList<>();
+        List<BytesRef> queryTerms = new ArrayList<>();
         for (int j = 0; j < numQueryTerms; ++j) {
           queryTerms.add(allTerms.get(random().nextInt(allTerms.size())));
         }
         final BooleanQuery.Builder bq = new BooleanQuery.Builder();
-        for (Term t : queryTerms) {
-          bq.add(new TermQuery(t), Occur.SHOULD);
+        for (BytesRef t : queryTerms) {
+          bq.add(new TermQuery(new Term(field, t)), Occur.SHOULD);
         }
         final Query q1 = new ConstantScoreQuery(bq.build());
-        final Query q2 = new TermInSetQuery(queryTerms);
+        final Query q2 = new TermInSetQuery(field, queryTerms);
         assertSameMatches(searcher, new BoostQuery(q1, boost), new BoostQuery(q2, boost), true);
       }
 
@@ -118,103 +115,72 @@ public class TermInSetQueryTest extends LuceneTestCase {
     }
   }
 
-  private TermInSetQuery termsQuery(boolean singleField, Term...terms) {
-    return termsQuery(singleField, Arrays.asList(terms));
-  }
-
-  private TermInSetQuery termsQuery(boolean singleField, Collection<Term> termList) {
-    if (!singleField) {
-      return new TermInSetQuery(new ArrayList<>(termList));
-    }
-    final TermInSetQuery filter;
-    List<BytesRef> bytes = new ArrayList<>();
-    String field = null;
-    for (Term term : termList) {
-        bytes.add(term.bytes());
-        if (field != null) {
-          assertEquals(term.field(), field);
-        }
-        field = term.field();
-    }
-    assertNotNull(field);
-    filter = new TermInSetQuery(field, bytes);
-    return filter;
-  }
-
   public void testHashCodeAndEquals() {
     int num = atLeast(100);
-    final boolean singleField = random().nextBoolean();
-    List<Term> terms = new ArrayList<>();
-    Set<Term> uniqueTerms = new HashSet<>();
+    List<BytesRef> terms = new ArrayList<>();
+    Set<BytesRef> uniqueTerms = new HashSet<>();
     for (int i = 0; i < num; i++) {
-      String field = "field" + (singleField ? "1" : random().nextInt(100));
       String string = TestUtil.randomRealisticUnicodeString(random());
-      terms.add(new Term(field, string));
-      uniqueTerms.add(new Term(field, string));
-      TermInSetQuery left = termsQuery(singleField ? random().nextBoolean() : false, uniqueTerms);
+      terms.add(new BytesRef(string));
+      uniqueTerms.add(new BytesRef(string));
+      TermInSetQuery left = new TermInSetQuery("field", uniqueTerms);
       Collections.shuffle(terms, random());
-      TermInSetQuery right = termsQuery(singleField ? random().nextBoolean() : false, terms);
+      TermInSetQuery right = new TermInSetQuery("field", terms);
       assertEquals(right, left);
       assertEquals(right.hashCode(), left.hashCode());
       if (uniqueTerms.size() > 1) {
-        List<Term> asList = new ArrayList<>(uniqueTerms);
+        List<BytesRef> asList = new ArrayList<>(uniqueTerms);
         asList.remove(0);
-        TermInSetQuery notEqual = termsQuery(singleField ? random().nextBoolean() : false, asList);
+        TermInSetQuery notEqual = new TermInSetQuery("field", asList);
         assertFalse(left.equals(notEqual));
         assertFalse(right.equals(notEqual));
       }
     }
 
-    TermInSetQuery tq1 = new TermInSetQuery(new Term("thing", "apple"));
-    TermInSetQuery tq2 = new TermInSetQuery(new Term("thing", "orange"));
+    TermInSetQuery tq1 = new TermInSetQuery("thing", new BytesRef("apple"));
+    TermInSetQuery tq2 = new TermInSetQuery("thing", new BytesRef("orange"));
     assertFalse(tq1.hashCode() == tq2.hashCode());
 
     // different fields with the same term should have differing hashcodes
-    tq1 = new TermInSetQuery(new Term("thing1", "apple"));
-    tq2 = new TermInSetQuery(new Term("thing2", "apple"));
+    tq1 = new TermInSetQuery("thing", new BytesRef("apple"));
+    tq2 = new TermInSetQuery("thing2", new BytesRef("apple"));
     assertFalse(tq1.hashCode() == tq2.hashCode());
   }
 
-  public void testSingleFieldEquals() {
+  public void testSimpleEquals() {
     // Two terms with the same hash code
     assertEquals("AaAaBB".hashCode(), "BBBBBB".hashCode());
-    TermInSetQuery left = termsQuery(true, new Term("id", "AaAaAa"), new Term("id", "AaAaBB"));
-    TermInSetQuery right = termsQuery(true, new Term("id", "AaAaAa"), new Term("id", "BBBBBB"));
+    TermInSetQuery left = new TermInSetQuery("id", new BytesRef("AaAaAa"), new BytesRef("AaAaBB"));
+    TermInSetQuery right = new TermInSetQuery("id", new BytesRef("AaAaAa"), new BytesRef("BBBBBB"));
     assertFalse(left.equals(right));
   }
 
   public void testToString() {
-    TermInSetQuery termsQuery = new TermInSetQuery(new Term("field1", "a"),
-                                              new Term("field1", "b"),
-                                              new Term("field1", "c"));
+    TermInSetQuery termsQuery = new TermInSetQuery("field1",
+        new BytesRef("a"), new BytesRef("b"), new BytesRef("c"));
     assertEquals("field1:a field1:b field1:c", termsQuery.toString());
   }
 
   public void testDedup() {
-    Query query1 = new TermInSetQuery(new Term("foo", "bar"));
-    Query query2 = new TermInSetQuery(new Term("foo", "bar"), new Term("foo", "bar"));
+    Query query1 = new TermInSetQuery("foo", new BytesRef("bar"));
+    Query query2 = new TermInSetQuery("foo", new BytesRef("bar"), new BytesRef("bar"));
     QueryUtils.checkEqual(query1, query2);
   }
 
   public void testOrderDoesNotMatter() {
     // order of terms if different
-    Query query1 = new TermInSetQuery(new Term("foo", "bar"), new Term("foo", "baz"));
-    Query query2 = new TermInSetQuery(new Term("foo", "baz"), new Term("foo", "bar"));
-    QueryUtils.checkEqual(query1, query2);
-
-    // order of fields is different
-    query1 = new TermInSetQuery(new Term("foo", "bar"), new Term("bar", "bar"));
-    query2 = new TermInSetQuery(new Term("bar", "bar"), new Term("foo", "bar"));
+    Query query1 = new TermInSetQuery("foo", new BytesRef("bar"), new BytesRef("baz"));
+    Query query2 = new TermInSetQuery("foo", new BytesRef("baz"), new BytesRef("bar"));
     QueryUtils.checkEqual(query1, query2);
   }
 
   public void testRamBytesUsed() {
-    List<Term> terms = new ArrayList<>();
+    List<BytesRef> terms = new ArrayList<>();
     final int numTerms = 1000 + random().nextInt(1000);
     for (int i = 0; i < numTerms; ++i) {
-      terms.add(new Term("f", RandomStrings.randomUnicodeOfLength(random(), 10)));
+      terms.add(new BytesRef(RandomStrings.randomUnicodeOfLength(random(), 10)));
     }
-    TermInSetQuery query = new TermInSetQuery(terms);
+    TermInSetQuery query = new TermInSetQuery("f", terms);
     final long actualRamBytesUsed = RamUsageTester.sizeOf(query);
     final long expectedRamBytesUsed = query.ramBytesUsed();
     // error margin within 5%
@@ -281,43 +247,40 @@ public class TermInSetQueryTest extends LuceneTestCase {
 
   }
 
-  public void testPullOneTermsEnumPerField() throws Exception {
+  public void testPullOneTermsEnum() throws Exception {
     Directory dir = newDirectory();
     RandomIndexWriter w = new RandomIndexWriter(random(), dir);
     Document doc = new Document();
     doc.add(new StringField("foo", "1", Store.NO));
-    doc.add(new StringField("bar", "2", Store.NO));
-    doc.add(new StringField("baz", "3", Store.NO));
     w.addDocument(doc);
     DirectoryReader reader = w.getReader();
     w.close();
     final AtomicInteger counter = new AtomicInteger();
     DirectoryReader wrapped = new TermsCountingDirectoryReaderWrapper(reader, counter);
 
-    final List<Term> terms = new ArrayList<>();
-    final Set<String> fields = new HashSet<>();
+    final List<BytesRef> terms = new ArrayList<>();
     // enough terms to avoid the rewrite
     final int numTerms = TestUtil.nextInt(random(), TermInSetQuery.BOOLEAN_REWRITE_TERM_COUNT_THRESHOLD + 1, 100);
     for (int i = 0; i < numTerms; ++i) {
-      final String field = RandomPicks.randomFrom(random(), new String[] {"foo", "bar", "baz"});
       final BytesRef term = new BytesRef(RandomStrings.randomUnicodeOfCodepointLength(random(), 10));
-      fields.add(field);
-      terms.add(new Term(field, term));
+      terms.add(term);
     }
 
-    new IndexSearcher(wrapped).count(new TermInSetQuery(terms));
-    assertEquals(fields.size(), counter.get());
+    assertEquals(0, new IndexSearcher(wrapped).count(new TermInSetQuery("bar", terms)));
+    assertEquals(0, counter.get()); // missing field
+    new IndexSearcher(wrapped).count(new TermInSetQuery("foo", terms));
+    assertEquals(1, counter.get());
     wrapped.close();
     dir.close();
   }
   
   public void testBinaryToString() {
-    TermInSetQuery query = new TermInSetQuery(new Term("field", new BytesRef(new byte[] { (byte) 0xff, (byte) 0xfe })));
+    TermInSetQuery query = new TermInSetQuery("field", new BytesRef(new byte[] { (byte) 0xff, (byte) 0xfe }));
     assertEquals("field:[ff fe]", query.toString());
   }
 
   public void testIsConsideredCostlyByQueryCache() throws IOException {
-    TermInSetQuery query = new TermInSetQuery(new Term("foo", "bar"), new Term("foo", "baz"));
+    TermInSetQuery query = new TermInSetQuery("foo", new BytesRef("bar"), new BytesRef("baz"));
     UsageTrackingQueryCachingPolicy policy = new UsageTrackingQueryCachingPolicy();
     assertFalse(policy.shouldCache(query));
     policy.onUse(query);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/43874fc5/lucene/facet/src/java/org/apache/lucene/facet/MultiFacetQuery.java
----------------------------------------------------------------------
diff --git a/lucene/facet/src/java/org/apache/lucene/facet/MultiFacetQuery.java b/lucene/facet/src/java/org/apache/lucene/facet/MultiFacetQuery.java
index a010709..72c2773 100644
--- a/lucene/facet/src/java/org/apache/lucene/facet/MultiFacetQuery.java
+++ b/lucene/facet/src/java/org/apache/lucene/facet/MultiFacetQuery.java
@@ -19,9 +19,9 @@ package org.apache.lucene.facet;
 import java.util.ArrayList;
 import java.util.Collection;
 
-import org.apache.lucene.index.Term;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermInSetQuery;
+import org.apache.lucene.util.BytesRef;
 
 /**
  * A multi-terms {@link Query} over a {@link FacetField}.
@@ -38,7 +38,7 @@ public class MultiFacetQuery extends TermInSetQuery {
    * Creates a new {@code MultiFacetQuery} filtering the query on the given dimension.
    */
   public MultiFacetQuery(final FacetsConfig facetsConfig, final String dimension, final String[]... paths) {
-    super(toTerms(facetsConfig.getDimConfig(dimension), dimension, paths));
+    super(facetsConfig.getDimConfig(dimension).indexFieldName, toTerms(dimension, paths));
   }
 
   /**
@@ -47,14 +47,13 @@ public class MultiFacetQuery extends TermInSetQuery {
    * <b>NOTE:</b>Uses FacetsConfig.DEFAULT_DIM_CONFIG.
    */
   public MultiFacetQuery(final String dimension, final String[]... paths) {
-    super(toTerms(FacetsConfig.DEFAULT_DIM_CONFIG, dimension, paths));
+    super(FacetsConfig.DEFAULT_DIM_CONFIG.indexFieldName, toTerms(dimension, paths));
   }
 
-  static Collection<Term> toTerms(final FacetsConfig.DimConfig dimConfig, final String dimension,
-          final String[]... paths) {
-    final Collection<Term> terms = new ArrayList<>(paths.length);
+  static Collection<BytesRef> toTerms(final String dimension, final String[]... paths) {
+    final Collection<BytesRef> terms = new ArrayList<>(paths.length);
     for (String[] path : paths)
-      terms.add(FacetQuery.toTerm(dimConfig, dimension, path));
+      terms.add(new BytesRef(FacetsConfig.pathToString(dimension, path)));
     return terms;
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/43874fc5/lucene/spatial-extras/src/test/org/apache/lucene/spatial/prefix/NumberRangeFacetsTest.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/prefix/NumberRangeFacetsTest.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/prefix/NumberRangeFacetsTest.java
index bb26a2e..3cdf5e9 100644
--- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/prefix/NumberRangeFacetsTest.java
+++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/prefix/NumberRangeFacetsTest.java
@@ -24,7 +24,6 @@ import java.util.List;
 
 import com.carrotsearch.randomizedtesting.annotations.Repeat;
 import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.Term;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.SimpleCollector;
 import org.apache.lucene.search.TermInSetQuery;
@@ -36,6 +35,7 @@ import org.apache.lucene.spatial.prefix.tree.DateRangePrefixTree;
 import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree;
 import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape;
 import org.apache.lucene.util.Bits;
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.FixedBitSet;
 import org.junit.Before;
 import org.junit.Test;
@@ -127,12 +127,12 @@ public class NumberRangeFacetsTest extends StrategyTestCase {
         Collections.shuffle(acceptFieldIds, random());
         acceptFieldIds = acceptFieldIds.subList(0, randomInt(acceptFieldIds.size()));
         if (!acceptFieldIds.isEmpty()) {
-          List<Term> terms = new ArrayList<>();
+          List<BytesRef> terms = new ArrayList<>();
           for (Integer acceptDocId : acceptFieldIds) {
-            terms.add(new Term("id", acceptDocId.toString()));
+            terms.add(new BytesRef(acceptDocId.toString()));
           }
 
-          topAcceptDocs = searchForDocBits(new TermInSetQuery(terms));
+          topAcceptDocs = searchForDocBits(new TermInSetQuery("id", terms));
         }
       }
 


[31/38] lucene-solr:jira/solr-9857: SOLR-9926: Allow passing arbitrary java system properties to zkcli.

Posted by ab...@apache.org.
SOLR-9926: Allow passing arbitrary java system properties to zkcli.


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

Branch: refs/heads/jira/solr-9857
Commit: 9f58b6cd177f72b226c83adbb965cfe08d61d2fb
Parents: 57934ba
Author: markrmiller <ma...@apache.org>
Authored: Wed Jan 18 21:23:36 2017 -0500
Committer: markrmiller <ma...@apache.org>
Committed: Wed Jan 18 21:23:36 2017 -0500

----------------------------------------------------------------------
 solr/CHANGES.txt                            | 2 ++
 solr/server/scripts/cloud-scripts/zkcli.bat | 2 +-
 solr/server/scripts/cloud-scripts/zkcli.sh  | 2 +-
 3 files changed, 4 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9f58b6cd/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 82c3d2b..aab5116 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -100,6 +100,8 @@ New Features
 * SOLR-9836: Add ability to recover from leader when index corruption is detected on SolrCore creation.
   (Mike Drob via Mark Miller)
 
+* SOLR-9926: Allow passing arbitrary java system properties to zkcli. (Hrishikesh Gadre via Mark Miller)
+
 Bug Fixes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9f58b6cd/solr/server/scripts/cloud-scripts/zkcli.bat
----------------------------------------------------------------------
diff --git a/solr/server/scripts/cloud-scripts/zkcli.bat b/solr/server/scripts/cloud-scripts/zkcli.bat
index 0e4359c..c372685 100644
--- a/solr/server/scripts/cloud-scripts/zkcli.bat
+++ b/solr/server/scripts/cloud-scripts/zkcli.bat
@@ -21,5 +21,5 @@ REM  -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCrede
 REM  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD ^
 REM  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD
 
-"%JVM%" %SOLR_ZK_CREDS_AND_ACLS% -Dlog4j.configuration="%LOG4J_CONFIG%" ^
+"%JVM%" %SOLR_ZK_CREDS_AND_ACLS% %ZKCLI_JVM_FLAGS% -Dlog4j.configuration="%LOG4J_CONFIG%" ^
 -classpath "%SDIR%\..\..\solr-webapp\webapp\WEB-INF\lib\*;%SDIR%\..\..\lib\ext\*" org.apache.solr.cloud.ZkCLI %*

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9f58b6cd/solr/server/scripts/cloud-scripts/zkcli.sh
----------------------------------------------------------------------
diff --git a/solr/server/scripts/cloud-scripts/zkcli.sh b/solr/server/scripts/cloud-scripts/zkcli.sh
index e37b6da..df43265 100755
--- a/solr/server/scripts/cloud-scripts/zkcli.sh
+++ b/solr/server/scripts/cloud-scripts/zkcli.sh
@@ -21,6 +21,6 @@ fi
 #  -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD \
 #  -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD"
 
-PATH=$JAVA_HOME/bin:$PATH $JVM $SOLR_ZK_CREDS_AND_ACLS  -Dlog4j.configuration=$log4j_config \
+PATH=$JAVA_HOME/bin:$PATH $JVM $SOLR_ZK_CREDS_AND_ACLS $ZKCLI_JVM_FLAGS -Dlog4j.configuration=$log4j_config \
 -classpath "$sdir/../../solr-webapp/webapp/WEB-INF/lib/*:$sdir/../../lib/ext/*" org.apache.solr.cloud.ZkCLI ${1+"$@"}
 


[10/38] lucene-solr:jira/solr-9857: SOLR-9786: additional test related to TermInSetQuery now requiring all terms in same field

Posted by ab...@apache.org.
SOLR-9786: additional test related to TermInSetQuery now requiring all terms in same field


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

Branch: refs/heads/jira/solr-9857
Commit: 7d7e5d2246d69843f259b9815332a24dc621d9e7
Parents: 1acd2ee
Author: yonik <yo...@apache.org>
Authored: Tue Jan 17 10:20:02 2017 -0500
Committer: yonik <yo...@apache.org>
Committed: Tue Jan 17 10:20:02 2017 -0500

----------------------------------------------------------------------
 .../org/apache/solr/search/TestSolrQueryParser.java  | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/7d7e5d22/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java b/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java
index 76b441b..20c1907 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java
@@ -259,6 +259,21 @@ public class TestSolrQueryParser extends SolrTestCaseJ4 {
     }
     assertEquals(26, ((TermInSetQuery)qq).getTermData().size());
 
+    // test terms queries of two different fields (LUCENE-7637 changed to require all terms be in the same field)
+    StringBuilder sb = new StringBuilder();
+    for (int i=0; i<17; i++) {
+      char letter = (char)('a'+i);
+      sb.append("foo_s:" + letter + " bar_s:" + letter + " ");
+    }
+    qParser = QParser.getParser(sb.toString(), req);
+    qParser.setIsFilter(true); // this may change in the future
+    q = qParser.getQuery();
+    assertEquals(2, ((BooleanQuery)q).clauses().size());
+    for (BooleanClause clause : ((BooleanQuery)q).clauses()) {
+      qq = clause.getQuery();
+      assertEquals(17, ((TermInSetQuery)qq).getTermData().size());
+    }
+
     req.close();
   }
 


[33/38] lucene-solr:jira/solr-9857: SOLR-9984: Remove GenericHadoopAuthPlugin (HadoopAuthPlugin is there)

Posted by ab...@apache.org.
SOLR-9984: Remove GenericHadoopAuthPlugin (HadoopAuthPlugin is there)


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

Branch: refs/heads/jira/solr-9857
Commit: bb35732eef90fc0ba7862d2c123c7e16356d2a0b
Parents: 1a05d6f
Author: Ishan Chattopadhyaya <ic...@gmail.com>
Authored: Thu Jan 19 10:02:13 2017 +0530
Committer: Ishan Chattopadhyaya <ic...@gmail.com>
Committed: Thu Jan 19 10:02:13 2017 +0530

----------------------------------------------------------------------
 solr/CHANGES.txt                                |  8 -----
 .../solr/security/GenericHadoopAuthPlugin.java  | 31 --------------------
 2 files changed, 39 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bb35732e/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 62b8818..aab5116 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -94,12 +94,6 @@ Jetty 9.3.14.v20161028
 Detailed Change List
 ----------------------
 
-Upgrade Notes
-----------------------
-
-* SOLR-9984: GenericHadoopAuthPlugin is deprecated in favor of HadoopAuthPlugin. Simply changing the
-  name of the class in the security configurations should suffice while upgrading.
-
 New Features
 ----------------------
 
@@ -128,8 +122,6 @@ Other Changes
 ----------------------
 * SOLR-9980: Expose configVersion in core admin status (Jessica Cheng Mallet via Tom�s Fern�ndez L�bbe)
 
-* SOLR-9984: Deprecate GenericHadoopAuthPlugin in favor of HadoopAuthPlugin (Ishan Chattopadhyaya)
-
 ==================  6.4.0 ==================
 
 Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bb35732e/solr/core/src/java/org/apache/solr/security/GenericHadoopAuthPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/GenericHadoopAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/GenericHadoopAuthPlugin.java
deleted file mode 100644
index 3d63fd6..0000000
--- a/solr/core/src/java/org/apache/solr/security/GenericHadoopAuthPlugin.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.security;
-
-import org.apache.solr.core.CoreContainer;
-
-/**
- *  * @deprecated Use {@link HadoopAuthPlugin}. For backcompat against Solr 6.4.
- */
-@Deprecated
-public class GenericHadoopAuthPlugin extends HadoopAuthPlugin {
-
-  public GenericHadoopAuthPlugin(CoreContainer coreContainer) {
-    super(coreContainer);
-  }
-
-}
\ No newline at end of file


[19/38] lucene-solr:jira/solr-9857: LUCENE-7640: Speed up PointValues#estimatePointCount with Relation.CELL_INSIDE_QUERY.

Posted by ab...@apache.org.
LUCENE-7640: Speed up PointValues#estimatePointCount with Relation.CELL_INSIDE_QUERY.


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

Branch: refs/heads/jira/solr-9857
Commit: 71aa463d4bbdfc03efb11b52ed2c4ce51d49bfb3
Parents: 3404677
Author: Adrien Grand <jp...@gmail.com>
Authored: Wed Jan 18 15:07:06 2017 +0100
Committer: Adrien Grand <jp...@gmail.com>
Committed: Wed Jan 18 15:07:06 2017 +0100

----------------------------------------------------------------------
 .../org/apache/lucene/util/bkd/BKDReader.java   |  45 ++++-
 .../lucene60/TestLucene60PointsFormat.java      | 192 ++++++++++++++++++-
 .../org/apache/lucene/util/bkd/TestBKD.java     |  90 +++++++++
 3 files changed, 319 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71aa463d/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
index 4089d82..e120435 100644
--- a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
+++ b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
@@ -223,6 +223,41 @@ public final class BKDReader extends PointValues implements Accountable {
     
     /** Only valid after pushLeft or pushRight, not pop! */
     public abstract long getLeafBlockFP();
+
+    /** Return the number of leaves below the current node. */
+    public int getNumLeaves() {
+      int leftMostLeafNode = nodeID;
+      while (leftMostLeafNode < leafNodeOffset) {
+        leftMostLeafNode = leftMostLeafNode * 2;
+      }
+      int rightMostLeafNode = nodeID;
+      while (rightMostLeafNode < leafNodeOffset) {
+        rightMostLeafNode = rightMostLeafNode * 2 + 1;
+      }
+      final int numLeaves;
+      if (rightMostLeafNode >= leftMostLeafNode) {
+        // both are on the same level
+        numLeaves = rightMostLeafNode - leftMostLeafNode + 1;
+      } else {
+        // left is one level deeper than right
+        numLeaves = rightMostLeafNode - leftMostLeafNode + 1 + leafNodeOffset;
+      }
+      assert numLeaves == getNumLeavesSlow(nodeID) : numLeaves + " " + getNumLeavesSlow(nodeID);
+      return numLeaves;
+    }
+
+    // for assertions
+    private int getNumLeavesSlow(int node) {
+      if (node >= 2 * leafNodeOffset) {
+        return 0;
+      } else if (node >= leafNodeOffset) {
+        return 1;
+      } else {
+        final int leftCount = getNumLeavesSlow(node * 2);
+        final int rightCount = getNumLeavesSlow(node * 2 + 1);
+        return leftCount + rightCount;
+      }
+    }
   }
 
   /** Reads the original simple yet heap-heavy index format */
@@ -716,13 +751,11 @@ public final class BKDReader extends PointValues implements Accountable {
     if (r == Relation.CELL_OUTSIDE_QUERY) {
       // This cell is fully outside of the query shape: stop recursing
       return 0L;
+    } else if (r == Relation.CELL_INSIDE_QUERY) {
+      return (long) maxPointsInLeafNode * state.index.getNumLeaves();
     } else if (state.index.isLeafNode()) {
-      if (r == Relation.CELL_INSIDE_QUERY) {
-        return maxPointsInLeafNode;
-      } else {
-        // Assume half the points matched
-        return (maxPointsInLeafNode + 1) / 2;
-      }
+      // Assume half the points matched
+      return (maxPointsInLeafNode + 1) / 2;
     } else {
       
       // Non-leaf node: recurse on the split left and right nodes

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71aa463d/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java b/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
index afa8ec4..3a08bfa 100644
--- a/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
+++ b/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
@@ -18,29 +18,43 @@ package org.apache.lucene.codecs.lucene60;
 
 
 import java.io.IOException;
+import java.util.Arrays;
 
 import org.apache.lucene.codecs.Codec;
 import org.apache.lucene.codecs.FilterCodec;
 import org.apache.lucene.codecs.PointsFormat;
 import org.apache.lucene.codecs.PointsReader;
 import org.apache.lucene.codecs.PointsWriter;
+import org.apache.lucene.document.BinaryPoint;
+import org.apache.lucene.document.Document;
 import org.apache.lucene.index.BasePointsFormatTestCase;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.PointValues;
 import org.apache.lucene.index.SegmentReadState;
 import org.apache.lucene.index.SegmentWriteState;
+import org.apache.lucene.index.PointValues.IntersectVisitor;
+import org.apache.lucene.index.PointValues.Relation;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.StringHelper;
 import org.apache.lucene.util.TestUtil;
+import org.apache.lucene.util.bkd.BKDWriter;
 
 /**
  * Tests Lucene60PointsFormat
  */
 public class TestLucene60PointsFormat extends BasePointsFormatTestCase {
   private final Codec codec;
+  private final int maxPointsInLeafNode;
   
   public TestLucene60PointsFormat() {
     // standard issue
     Codec defaultCodec = TestUtil.getDefaultCodec();
     if (random().nextBoolean()) {
       // randomize parameters
-      int maxPointsInLeafNode = TestUtil.nextInt(random(), 50, 500);
+      maxPointsInLeafNode = TestUtil.nextInt(random(), 50, 500);
       double maxMBSortInHeap = 3.0 + (3*random().nextDouble());
       if (VERBOSE) {
         System.out.println("TEST: using Lucene60PointsFormat with maxPointsInLeafNode=" + maxPointsInLeafNode + " and maxMBSortInHeap=" + maxMBSortInHeap);
@@ -66,6 +80,7 @@ public class TestLucene60PointsFormat extends BasePointsFormatTestCase {
     } else {
       // standard issue
       codec = defaultCodec;
+      maxPointsInLeafNode = BKDWriter.DEFAULT_MAX_POINTS_IN_LEAF_NODE;
     }
   }
 
@@ -79,5 +94,178 @@ public class TestLucene60PointsFormat extends BasePointsFormatTestCase {
     assumeFalse("TODO: mess with the parameters and test gets angry!", codec instanceof FilterCodec);
     super.testMergeStability();
   }
-  
+
+  public void testEstimatePointCount() throws IOException {
+    Directory dir = newDirectory();
+    IndexWriter w = new IndexWriter(dir, newIndexWriterConfig());
+    byte[] pointValue = new byte[3];
+    byte[] uniquePointValue = new byte[3];
+    random().nextBytes(uniquePointValue);
+    final int numDocs = atLeast(10000); // make sure we have several leaves
+    for (int i = 0; i < numDocs; ++i) {
+      Document doc = new Document();
+      if (i == numDocs / 2) {
+        doc.add(new BinaryPoint("f", uniquePointValue));
+      } else {
+        do {
+          random().nextBytes(pointValue);
+        } while (Arrays.equals(pointValue, uniquePointValue));
+        doc.add(new BinaryPoint("f", pointValue));
+      }
+      w.addDocument(doc);
+    }
+    w.forceMerge(1);
+    final IndexReader r = DirectoryReader.open(w);
+    w.close();
+    final LeafReader lr = getOnlyLeafReader(r);
+    PointValues points = lr.getPointValues("f");
+
+    // If all points match, then the point count is numLeaves * maxPointsInLeafNode
+    final int numLeaves = (int) Math.ceil((double) numDocs / maxPointsInLeafNode);
+    assertEquals(numLeaves * maxPointsInLeafNode,
+        points.estimatePointCount(new IntersectVisitor() {
+          @Override
+          public void visit(int docID, byte[] packedValue) throws IOException {}
+          
+          @Override
+          public void visit(int docID) throws IOException {}
+          
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+            return Relation.CELL_INSIDE_QUERY;
+          }
+        }));
+
+    // Return 0 if no points match
+    assertEquals(0,
+        points.estimatePointCount(new IntersectVisitor() {
+          @Override
+          public void visit(int docID, byte[] packedValue) throws IOException {}
+          
+          @Override
+          public void visit(int docID) throws IOException {}
+          
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+            return Relation.CELL_OUTSIDE_QUERY;
+          }
+        }));
+
+    // If only one point matches, then the point count is (maxPointsInLeafNode + 1) / 2
+    assertEquals((maxPointsInLeafNode + 1) / 2,
+        points.estimatePointCount(new IntersectVisitor() {
+          @Override
+          public void visit(int docID, byte[] packedValue) throws IOException {}
+          
+          @Override
+          public void visit(int docID) throws IOException {}
+          
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+            if (StringHelper.compare(3, uniquePointValue, 0, maxPackedValue, 0) > 0 ||
+                StringHelper.compare(3, uniquePointValue, 0, minPackedValue, 0) < 0) {
+              return Relation.CELL_OUTSIDE_QUERY;
+            }
+            return Relation.CELL_CROSSES_QUERY;
+          }
+        }));
+
+    r.close();
+    dir.close();
+  }
+
+  // The tree is always balanced in the N dims case, and leaves are
+  // not all full so things are a bit different
+  public void testEstimatePointCount2Dims() throws IOException {
+    Directory dir = newDirectory();
+    IndexWriter w = new IndexWriter(dir, newIndexWriterConfig());
+    byte[][] pointValue = new byte[2][];
+    pointValue[0] = new byte[3];
+    pointValue[1] = new byte[3];
+    byte[][] uniquePointValue = new byte[2][];
+    uniquePointValue[0] = new byte[3];
+    uniquePointValue[1] = new byte[3];
+    random().nextBytes(uniquePointValue[0]);
+    random().nextBytes(uniquePointValue[1]);
+    final int numDocs = atLeast(10000); // make sure we have several leaves
+    for (int i = 0; i < numDocs; ++i) {
+      Document doc = new Document();
+      if (i == numDocs / 2) {
+        doc.add(new BinaryPoint("f", uniquePointValue));
+      } else {
+        do {
+          random().nextBytes(pointValue[0]);
+          random().nextBytes(pointValue[1]);
+        } while (Arrays.equals(pointValue[0], uniquePointValue[0]) || Arrays.equals(pointValue[1], uniquePointValue[1]));
+        doc.add(new BinaryPoint("f", pointValue));
+      }
+      w.addDocument(doc);
+    }
+    w.forceMerge(1);
+    final IndexReader r = DirectoryReader.open(w);
+    w.close();
+    final LeafReader lr = getOnlyLeafReader(r);
+    PointValues points = lr.getPointValues("f");
+
+    // With >1 dims, the tree is balanced
+    int actualMaxPointsInLeafNode = numDocs;
+    while (actualMaxPointsInLeafNode > maxPointsInLeafNode) {
+      actualMaxPointsInLeafNode = (actualMaxPointsInLeafNode + 1) / 2;
+    }
+
+    // If all points match, then the point count is numLeaves * maxPointsInLeafNode
+    final int numLeaves = Integer.highestOneBit((numDocs - 1) / actualMaxPointsInLeafNode) << 1;
+    assertEquals(numLeaves * actualMaxPointsInLeafNode,
+        points.estimatePointCount(new IntersectVisitor() {
+          @Override
+          public void visit(int docID, byte[] packedValue) throws IOException {}
+          
+          @Override
+          public void visit(int docID) throws IOException {}
+          
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+            return Relation.CELL_INSIDE_QUERY;
+          }
+        }));
+
+    // Return 0 if no points match
+    assertEquals(0,
+        points.estimatePointCount(new IntersectVisitor() {
+          @Override
+          public void visit(int docID, byte[] packedValue) throws IOException {}
+          
+          @Override
+          public void visit(int docID) throws IOException {}
+          
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+            return Relation.CELL_OUTSIDE_QUERY;
+          }
+        }));
+
+    // If only one point matches, then the point count is (actualMaxPointsInLeafNode + 1) / 2
+    assertEquals((actualMaxPointsInLeafNode + 1) / 2,
+        points.estimatePointCount(new IntersectVisitor() {
+          @Override
+          public void visit(int docID, byte[] packedValue) throws IOException {}
+          
+          @Override
+          public void visit(int docID) throws IOException {}
+          
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+            for (int dim = 0; dim < 2; ++dim) {
+              if (StringHelper.compare(3, uniquePointValue[0], 0, maxPackedValue, dim * 3) > 0 ||
+                  StringHelper.compare(3, uniquePointValue[0], 0, minPackedValue, dim * 3) < 0) {
+                return Relation.CELL_OUTSIDE_QUERY;
+              }
+            }
+            return Relation.CELL_CROSSES_QUERY;
+          }
+        }));
+
+    r.close();
+    dir.close();
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71aa463d/lucene/core/src/test/org/apache/lucene/util/bkd/TestBKD.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/util/bkd/TestBKD.java b/lucene/core/src/test/org/apache/lucene/util/bkd/TestBKD.java
index c5c5c1f..f01f058 100644
--- a/lucene/core/src/test/org/apache/lucene/util/bkd/TestBKD.java
+++ b/lucene/core/src/test/org/apache/lucene/util/bkd/TestBKD.java
@@ -1104,4 +1104,94 @@ public class TestBKD extends LuceneTestCase {
     in.close();
     dir.close();
   }
+
+  public void testEstimatePointCount() throws IOException {
+    Directory dir = newDirectory();
+    final int numValues = atLeast(10000); // make sure to have multiple leaves
+    final int maxPointsInLeafNode = TestUtil.nextInt(random(), 50, 500);
+    final int numBytesPerDim = TestUtil.nextInt(random(), 1, 4);
+    final byte[] pointValue = new byte[numBytesPerDim];
+    final byte[] uniquePointValue = new byte[numBytesPerDim];
+    random().nextBytes(uniquePointValue);
+
+    BKDWriter w = new BKDWriter(numValues, dir, "_temp", 1, numBytesPerDim, maxPointsInLeafNode,
+        BKDWriter.DEFAULT_MAX_MB_SORT_IN_HEAP, numValues, true);
+    for (int i = 0; i < numValues; ++i) {
+      if (i == numValues / 2) {
+        w.add(uniquePointValue, i);
+      } else {
+        do {
+          random().nextBytes(pointValue);
+        } while (Arrays.equals(pointValue, uniquePointValue));
+        w.add(pointValue, i);
+      }
+    }
+    final long indexFP;
+    try (IndexOutput out = dir.createOutput("bkd", IOContext.DEFAULT)) {
+      indexFP = w.finish(out);
+      w.close();
+    }
+    
+    IndexInput pointsIn = dir.openInput("bkd", IOContext.DEFAULT);
+    pointsIn.seek(indexFP);
+    BKDReader points = new BKDReader(pointsIn);
+
+    int actualMaxPointsInLeafNode = numValues;
+    while (actualMaxPointsInLeafNode > maxPointsInLeafNode) {
+      actualMaxPointsInLeafNode = (actualMaxPointsInLeafNode + 1) / 2;
+    }
+
+    // If all points match, then the point count is numLeaves * maxPointsInLeafNode
+    final int numLeaves = Integer.highestOneBit((numValues - 1) / actualMaxPointsInLeafNode) << 1;
+    assertEquals(numLeaves * actualMaxPointsInLeafNode,
+        points.estimatePointCount(new IntersectVisitor() {
+          @Override
+          public void visit(int docID, byte[] packedValue) throws IOException {}
+          
+          @Override
+          public void visit(int docID) throws IOException {}
+          
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+            return Relation.CELL_INSIDE_QUERY;
+          }
+        }));
+
+    // Return 0 if no points match
+    assertEquals(0,
+        points.estimatePointCount(new IntersectVisitor() {
+          @Override
+          public void visit(int docID, byte[] packedValue) throws IOException {}
+          
+          @Override
+          public void visit(int docID) throws IOException {}
+          
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+            return Relation.CELL_OUTSIDE_QUERY;
+          }
+        }));
+
+    // If only one point matches, then the point count is (actualMaxPointsInLeafNode + 1) / 2
+    assertEquals((actualMaxPointsInLeafNode + 1) / 2,
+        points.estimatePointCount(new IntersectVisitor() {
+          @Override
+          public void visit(int docID, byte[] packedValue) throws IOException {}
+          
+          @Override
+          public void visit(int docID) throws IOException {}
+          
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+            if (StringHelper.compare(3, uniquePointValue, 0, maxPackedValue, 0) > 0 ||
+                StringHelper.compare(3, uniquePointValue, 0, minPackedValue, 0) < 0) {
+              return Relation.CELL_OUTSIDE_QUERY;
+            }
+            return Relation.CELL_CROSSES_QUERY;
+          }
+        }));
+
+    pointsIn.close();
+    dir.close();
+  }
 }


[03/38] lucene-solr:jira/solr-9857: Remove four unnecessary @Override annotations in SolrQueryBuilder (test) classes.

Posted by ab...@apache.org.
Remove four unnecessary @Override annotations in SolrQueryBuilder (test) classes.


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

Branch: refs/heads/jira/solr-9857
Commit: 230190065ca96c6ecc45e581a56f856888c2e321
Parents: 649c58d
Author: Christine Poerschke <cp...@apache.org>
Authored: Mon Jan 16 18:14:36 2017 +0000
Committer: Christine Poerschke <cp...@apache.org>
Committed: Mon Jan 16 18:14:36 2017 +0000

----------------------------------------------------------------------
 .../org/apache/solr/search/ApacheLuceneSolrNearQueryBuilder.java    | 1 -
 solr/core/src/test/org/apache/solr/search/GoodbyeQueryBuilder.java  | 1 -
 solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java    | 1 -
 solr/core/src/test/org/apache/solr/search/HelloQueryBuilder.java    | 1 -
 4 files changed, 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/23019006/solr/core/src/test/org/apache/solr/search/ApacheLuceneSolrNearQueryBuilder.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/ApacheLuceneSolrNearQueryBuilder.java b/solr/core/src/test/org/apache/solr/search/ApacheLuceneSolrNearQueryBuilder.java
index 135ec45..bbc081a 100644
--- a/solr/core/src/test/org/apache/solr/search/ApacheLuceneSolrNearQueryBuilder.java
+++ b/solr/core/src/test/org/apache/solr/search/ApacheLuceneSolrNearQueryBuilder.java
@@ -35,7 +35,6 @@ public class ApacheLuceneSolrNearQueryBuilder extends SolrQueryBuilder {
     super(defaultField, analyzer, req, queryFactory);
   }
 
-  @Override
   public Query getQuery(Element e) throws ParserException {
     final String fieldName = DOMUtils.getAttributeWithInheritanceOrFail(e, "fieldName");
     final SpanQuery[] spanQueries = new SpanQuery[]{

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/23019006/solr/core/src/test/org/apache/solr/search/GoodbyeQueryBuilder.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/GoodbyeQueryBuilder.java b/solr/core/src/test/org/apache/solr/search/GoodbyeQueryBuilder.java
index af258d4..93f4a1b 100644
--- a/solr/core/src/test/org/apache/solr/search/GoodbyeQueryBuilder.java
+++ b/solr/core/src/test/org/apache/solr/search/GoodbyeQueryBuilder.java
@@ -31,7 +31,6 @@ public class GoodbyeQueryBuilder extends SolrQueryBuilder {
     super(defaultField, analyzer, req, queryFactory);
   }
 
-  @Override
   public Query getQuery(Element e) throws ParserException {
     return new MatchNoDocsQuery();
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/23019006/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java b/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java
index 14a8aac..c38fb6b 100644
--- a/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java
+++ b/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java
@@ -35,7 +35,6 @@ public class HandyQueryBuilder extends SolrQueryBuilder {
     super(defaultField, analyzer, req, queryFactory);
   }
 
-  @Override
   public Query getQuery(Element e) throws ParserException {
     final BooleanQuery.Builder bq = new BooleanQuery.Builder();
     final Query lhsQ = getSubQuery(e, "Left");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/23019006/solr/core/src/test/org/apache/solr/search/HelloQueryBuilder.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/HelloQueryBuilder.java b/solr/core/src/test/org/apache/solr/search/HelloQueryBuilder.java
index 642047f..8ea98f1 100644
--- a/solr/core/src/test/org/apache/solr/search/HelloQueryBuilder.java
+++ b/solr/core/src/test/org/apache/solr/search/HelloQueryBuilder.java
@@ -31,7 +31,6 @@ public class HelloQueryBuilder extends SolrQueryBuilder {
     super(defaultField, analyzer, req, queryFactory);
   }
 
-  @Override
   public Query getQuery(Element e) throws ParserException {
     return new MatchAllDocsQuery();
   }


[14/38] lucene-solr:jira/solr-9857: SOLR-9975: add SpellCheckComponentTest.testCollateExtendedResultsWithJsonNl method

Posted by ab...@apache.org.
SOLR-9975: add SpellCheckComponentTest.testCollateExtendedResultsWithJsonNl method


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

Branch: refs/heads/jira/solr-9857
Commit: e816fbe233a9b667a9c30be63241c9400f5a0ebc
Parents: 637915b
Author: Christine Poerschke <cp...@apache.org>
Authored: Tue Jan 17 14:33:58 2017 +0000
Committer: Christine Poerschke <cp...@apache.org>
Committed: Tue Jan 17 15:55:51 2017 +0000

----------------------------------------------------------------------
 .../component/SpellCheckComponentTest.java      | 36 ++++++++++++++++++++
 1 file changed, 36 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e816fbe2/solr/core/src/test/org/apache/solr/handler/component/SpellCheckComponentTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/component/SpellCheckComponentTest.java b/solr/core/src/test/org/apache/solr/handler/component/SpellCheckComponentTest.java
index 0e11d44..37d02d9 100644
--- a/solr/core/src/test/org/apache/solr/handler/component/SpellCheckComponentTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/component/SpellCheckComponentTest.java
@@ -184,6 +184,42 @@ public class SpellCheckComponentTest extends SolrTestCaseJ4 {
   
 
   @Test
+  public void testCollateExtendedResultsWithJsonNl() throws Exception {
+    final String q = "documemtsss broens";
+    final String jsonNl = "map";
+    final boolean collateExtendedResults = random().nextBoolean();
+    final List<String> testsList = new ArrayList<String>();
+    if (collateExtendedResults) {
+      testsList.add("/spellcheck/collations/collation/collationQuery=='document brown'");
+      testsList.add("/spellcheck/collations/collation/hits==0");
+      switch (jsonNl) {
+        case "map":
+          testsList.add("/spellcheck/collations/collation/misspellingsAndCorrections/documemtsss=='document'");
+          testsList.add("/spellcheck/collations/collation/misspellingsAndCorrections/broens=='brown'");
+          break;
+        default:
+          fail("unexpected json.nl choice: "+jsonNl);
+          break;
+      }
+    } else {
+      testsList.add("/spellcheck/collations/collation=='document brown'");
+    }
+    final String[] testsArray = new String[testsList.size()];
+    implTestCollateExtendedResultsWithJsonNl(q, jsonNl, collateExtendedResults, testsList.toArray(testsArray));
+  }
+
+  private void implTestCollateExtendedResultsWithJsonNl(String q, String jsonNl, boolean collateExtendedResults, String ... tests) throws Exception {
+    final SolrQueryRequest solrQueryRequest = req(
+        CommonParams.QT, rh,
+        CommonParams.Q, q,
+        "json.nl", jsonNl,
+        SpellCheckComponent.COMPONENT_NAME, "true",
+        SpellingParams.SPELLCHECK_COLLATE_EXTENDED_RESULTS, Boolean.toString(collateExtendedResults),
+        SpellingParams.SPELLCHECK_COLLATE, "true");
+    assertJQ(solrQueryRequest, tests);
+  }
+
+  @Test
   public void testCorrectSpelling() throws Exception {
     // Make sure correct spellings are signaled in the response
     assertJQ(req("json.nl","map", "qt",rh, SpellCheckComponent.COMPONENT_NAME, "true",


[21/38] lucene-solr:jira/solr-9857: WordDelimiterGraphFilter can't correct offsets if a CharFilter had changed them

Posted by ab...@apache.org.
WordDelimiterGraphFilter can't correct offsets if a CharFilter had changed them


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

Branch: refs/heads/jira/solr-9857
Commit: 57626c9a998b45b27d38dc3610c8455e309cff45
Parents: 188a19e
Author: Mike McCandless <mi...@apache.org>
Authored: Wed Jan 18 11:03:08 2017 -0500
Committer: Mike McCandless <mi...@apache.org>
Committed: Wed Jan 18 11:04:01 2017 -0500

----------------------------------------------------------------------
 .../test/org/apache/lucene/analysis/core/TestRandomChains.java    | 3 +++
 1 file changed, 3 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57626c9a/lucene/analysis/common/src/test/org/apache/lucene/analysis/core/TestRandomChains.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/test/org/apache/lucene/analysis/core/TestRandomChains.java b/lucene/analysis/common/src/test/org/apache/lucene/analysis/core/TestRandomChains.java
index 0bd5e0a..8953f9f 100644
--- a/lucene/analysis/common/src/test/org/apache/lucene/analysis/core/TestRandomChains.java
+++ b/lucene/analysis/common/src/test/org/apache/lucene/analysis/core/TestRandomChains.java
@@ -80,6 +80,7 @@ import org.apache.lucene.analysis.miscellaneous.LimitTokenPositionFilter;
 import org.apache.lucene.analysis.miscellaneous.StemmerOverrideFilter.StemmerOverrideMap;
 import org.apache.lucene.analysis.miscellaneous.StemmerOverrideFilter;
 import org.apache.lucene.analysis.miscellaneous.WordDelimiterFilter;
+import org.apache.lucene.analysis.miscellaneous.WordDelimiterGraphFilter;
 import org.apache.lucene.analysis.path.PathHierarchyTokenizer;
 import org.apache.lucene.analysis.path.ReversePathHierarchyTokenizer;
 import org.apache.lucene.analysis.payloads.IdentityEncoder;
@@ -152,6 +153,8 @@ public class TestRandomChains extends BaseTokenStreamTestCase {
           ValidatingTokenFilter.class, 
           // TODO: needs to be a tokenizer, doesnt handle graph inputs properly (a shingle or similar following will then cause pain)
           WordDelimiterFilter.class,
+          // Cannot correct offsets when a char filter had changed them:
+          WordDelimiterGraphFilter.class,
           // clones of core's filters:
           org.apache.lucene.analysis.core.StopFilter.class,
           org.apache.lucene.analysis.core.LowerCaseFilter.class)) {


[22/38] lucene-solr:jira/solr-9857: SOLR-9979: Macro expansion should not be done in shard requests

Posted by ab...@apache.org.
SOLR-9979: Macro expansion should not be done in shard requests


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

Branch: refs/heads/jira/solr-9857
Commit: 68d246df003278ba0c35ae5f43872340b676a02f
Parents: 57626c9
Author: Tomas Fernandez Lobbe <tf...@apache.org>
Authored: Wed Jan 18 10:53:02 2017 -0800
Committer: Tomas Fernandez Lobbe <tf...@apache.org>
Committed: Wed Jan 18 10:53:02 2017 -0800

----------------------------------------------------------------------
 solr/CHANGES.txt                                |  2 +
 .../apache/solr/request/json/RequestUtil.java   | 16 ++++---
 .../org/apache/solr/TestDistributedSearch.java  | 46 ++++++++++++--------
 3 files changed, 40 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/68d246df/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 5fd8a9e..cfd7a4c 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -98,6 +98,8 @@ Bug Fixes
 * SOLR-9977: Fix config bug in DistribDocExpirationUpdateProcessorTest that allowed false assumptions
   about when index version changes (hossman)
 
+* SOLR-9979: Macro expansion should not be done in shard requests (Tom�s Fern�ndez L�bbe)
+
 Optimizations
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/68d246df/solr/core/src/java/org/apache/solr/request/json/RequestUtil.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/json/RequestUtil.java b/solr/core/src/java/org/apache/solr/request/json/RequestUtil.java
index 20efdc3..2529e74 100644
--- a/solr/core/src/java/org/apache/solr/request/json/RequestUtil.java
+++ b/solr/core/src/java/org/apache/solr/request/json/RequestUtil.java
@@ -147,14 +147,16 @@ public class RequestUtil {
       newMap.putAll( MultiMapSolrParams.asMultiMap(invariants) );
     }
 
-    String[] doMacrosStr = newMap.get("expandMacros");
-    boolean doMacros = true;
-    if (doMacrosStr != null) {
-      doMacros = "true".equals(doMacrosStr[0]);
-    }
+    if (!isShard) { // Don't expand macros in shard requests
+      String[] doMacrosStr = newMap.get("expandMacros");
+      boolean doMacros = true;
+      if (doMacrosStr != null) {
+        doMacros = "true".equals(doMacrosStr[0]);
+      }
 
-    if (doMacros) {
-      newMap = MacroExpander.expand(newMap);
+      if (doMacros) {
+        newMap = MacroExpander.expand(newMap);
+      }
     }
     // Set these params as soon as possible so if there is an error processing later, things like
     // "wt=json" will take effect from the defaults.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/68d246df/solr/core/src/test/org/apache/solr/TestDistributedSearch.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/TestDistributedSearch.java b/solr/core/src/test/org/apache/solr/TestDistributedSearch.java
index a5cc80c..24ab689 100644
--- a/solr/core/src/test/org/apache/solr/TestDistributedSearch.java
+++ b/solr/core/src/test/org/apache/solr/TestDistributedSearch.java
@@ -84,6 +84,7 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
   String tdate_b = "b_n_tdt";
   
   String oddField="oddField_s";
+  String s1="a_s";
   String missingField="ignore_exception__missing_but_valid_field_t";
   String invalidField="ignore_exception__invalid_field_not_in_schema";
 
@@ -111,44 +112,49 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
            "foo_sev_enum", "Medium",
            tdate_a, "2010-04-20T11:00:00Z",
            tdate_b, "2009-08-20T11:00:00Z",
-           "foo_f", 1.414f, "foo_b", "true", "foo_d", 1.414d);
+           "foo_f", 1.414f, "foo_b", "true", "foo_d", 1.414d, 
+           s1, "z${foo}");
     indexr(id,2, i1, 50 , tlong, 50,t1,"to come to the aid of their country.",
            "foo_sev_enum", "Medium",
            "foo_sev_enum", "High",
            tdate_a, "2010-05-02T11:00:00Z",
-           tdate_b, "2009-11-02T11:00:00Z");
+           tdate_b, "2009-11-02T11:00:00Z",
+           s1, "z${foo}");
     indexr(id,3, i1, 2, tlong, 2,t1,"how now brown cow",
-           tdate_a, "2010-05-03T11:00:00Z");
+           tdate_a, "2010-05-03T11:00:00Z",
+           s1, "z${foo}");
     indexr(id,4, i1, -100 ,tlong, 101,
            t1,"the quick fox jumped over the lazy dog", 
            tdate_a, "2010-05-03T11:00:00Z",
-           tdate_b, "2010-05-03T11:00:00Z");
+           tdate_b, "2010-05-03T11:00:00Z",
+           s1, "a");
     indexr(id,5, i1, 500, tlong, 500 ,
            t1,"the quick fox jumped way over the lazy dog", 
-           tdate_a, "2010-05-05T11:00:00Z");
-    indexr(id,6, i1, -600, tlong, 600 ,t1,"humpty dumpy sat on a wall");
-    indexr(id,7, i1, 123, tlong, 123 ,t1,"humpty dumpy had a great fall");
+           tdate_a, "2010-05-05T11:00:00Z",
+           s1, "b");
+    indexr(id,6, i1, -600, tlong, 600 ,t1,"humpty dumpy sat on a wall", s1, "c");
+    indexr(id,7, i1, 123, tlong, 123 ,t1,"humpty dumpy had a great fall", s1, "d");
     indexr(id,8, i1, 876, tlong, 876,
            tdate_b, "2010-01-05T11:00:00Z",
            "foo_sev_enum", "High",
-           t1,"all the kings horses and all the kings men");
-    indexr(id,9, i1, 7, tlong, 7,t1,"couldn't put humpty together again");
+           t1,"all the kings horses and all the kings men", s1, "e");
+    indexr(id,9, i1, 7, tlong, 7,t1,"couldn't put humpty together again", s1, "f");
 
     commit();  // try to ensure there's more than one segment
 
-    indexr(id,10, i1, 4321, tlong, 4321,t1,"this too shall pass");
+    indexr(id,10, i1, 4321, tlong, 4321,t1,"this too shall pass", s1, "g");
     indexr(id,11, i1, -987, tlong, 987,
            "foo_sev_enum", "Medium",
-           t1,"An eye for eye only ends up making the whole world blind.");
+           t1,"An eye for eye only ends up making the whole world blind.", s1, "h");
     indexr(id,12, i1, 379, tlong, 379,
-           t1,"Great works are performed, not by strength, but by perseverance.");
+           t1,"Great works are performed, not by strength, but by perseverance.", s1, "i");
     indexr(id,13, i1, 232, tlong, 232,
            t1,"no eggs on wall, lesson learned", 
-           oddField, "odd man out");
+           oddField, "odd man out", s1, "j");
 
-    indexr(id, "1001", "lowerfilt", "toyota"); // for spellcheck
+    indexr(id, "1001", "lowerfilt", "toyota", s1, "k"); // for spellcheck
 
-    indexr(id, 14, "SubjectTerms_mfacet", new String[]  {"mathematical models", "mathematical analysis"});
+    indexr(id, 14, "SubjectTerms_mfacet", new String[]  {"mathematical models", "mathematical analysis"}, s1, "l");
     indexr(id, 15, "SubjectTerms_mfacet", new String[]  {"test 1", "test 2", "test3"});
     indexr(id, 16, "SubjectTerms_mfacet", new String[]  {"test 1", "test 2", "test3"});
     String[] vals = new String[100];
@@ -867,13 +873,19 @@ public class TestDistributedSearch extends BaseDistributedSearchTestCase {
     // Try to get better coverage for refinement queries by turning off over requesting.
     // This makes it much more likely that we may not get the top facet values and hence
     // we turn of that checking.
-    handle.put("facet_fields", SKIPVAL);    
+    handle.put("facet_fields", SKIPVAL);
     query("q","*:*", "rows",0, "facet","true", "facet.field",t1,"facet.limit",5, "facet.shard.limit",5);
     // check a complex key name
     query("q","*:*", "rows",0, "facet","true", "facet.field","{!key='$a b/c \\' \\} foo'}"+t1,"facet.limit",5, "facet.shard.limit",5);
     query("q","*:*", "rows",0, "facet","true", "facet.field","{!key='$a'}"+t1,"facet.limit",5, "facet.shard.limit",5);
     handle.remove("facet_fields");
-
+    // Make sure there is no macro expansion for field values
+    query("q","*:*", "rows",0, "facet","true", "facet.field",s1,"facet.limit",5, "facet.shard.limit",5);
+    query("q","*:*", "rows",0, "facet","true", "facet.field",s1,"facet.limit",5, "facet.shard.limit",5, "expandMacros", "true");
+    query("q","*:*", "rows",0, "facet","true", "facet.field",s1,"facet.limit",5, "facet.shard.limit",5, "expandMacros", "false");
+    // Macro expansion should still work for the parameters
+    query("q","*:*", "rows",0, "facet","true", "facet.field","${foo}", "f.${foo}.mincount", 1, "foo", s1);
+    query("q","*:*", "rows",0, "facet","true", "facet.field","${foo}", "f.${foo}.mincount", 1, "foo", s1, "expandMacros", "true");
 
     // index the same document to two servers and make sure things
     // don't blow up.


[11/38] lucene-solr:jira/solr-9857: LUCENE-7619: add WordDelimiterGraphFilter (replacing WordDelimiterFilter) to produce a correct token stream graph when splitting words

Posted by ab...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/test-framework/src/java/org/apache/lucene/analysis/TokenStreamToDot.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/analysis/TokenStreamToDot.java b/lucene/test-framework/src/java/org/apache/lucene/analysis/TokenStreamToDot.java
index 4e8eeb8..64923db 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/analysis/TokenStreamToDot.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/analysis/TokenStreamToDot.java
@@ -93,7 +93,10 @@ public class TokenStreamToDot {
         final int endOffset = offsetAtt.endOffset();
         //System.out.println("start=" + startOffset + " end=" + endOffset + " len=" + inputText.length());
         if (inputText != null) {
-          arcLabel += " / " + inputText.substring(startOffset, endOffset);
+          String fragment = inputText.substring(startOffset, endOffset);
+          if (fragment.equals(termAtt.toString()) == false) {
+            arcLabel += " / " + fragment;
+          }
         } else {
           arcLabel += " / " + startOffset + "-" + endOffset;
         }


[09/38] lucene-solr:jira/solr-9857: Various fixes and updates for index sorting on flush

Posted by ab...@apache.org.
Various fixes and updates for index sorting on flush

* IndexWriter.validateIndexSort now throws a CorruptIndexException if a segment created by version >= 6.5.0 is not sorted (already applied in branch_6x)
* Removes unneeded check in AssertingLiveDocsFormat (already applied in branch_6x)
* Removes try/finally block when stored fields consumer finishes (already applied in branch_6x).


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

Branch: refs/heads/jira/solr-9857
Commit: 1acd2ee2bbe2ccc3a0607df5447e6216f9b6eb9a
Parents: ed513fd
Author: Jim Ferenczi <ji...@elastic.co>
Authored: Tue Jan 17 14:22:47 2017 +0100
Committer: Jim Ferenczi <ji...@elastic.co>
Committed: Tue Jan 17 14:22:47 2017 +0100

----------------------------------------------------------------------
 .../org/apache/lucene/index/DefaultIndexingChain.java     |  5 +----
 .../src/java/org/apache/lucene/index/IndexWriter.java     | 10 +++++-----
 .../java/org/apache/lucene/index/SortingLeafReader.java   |  2 +-
 .../lucene/codecs/asserting/AssertingLiveDocsFormat.java  |  9 ++-------
 4 files changed, 9 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1acd2ee2/lucene/core/src/java/org/apache/lucene/index/DefaultIndexingChain.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/DefaultIndexingChain.java b/lucene/core/src/java/org/apache/lucene/index/DefaultIndexingChain.java
index 197ab31..b118c13 100644
--- a/lucene/core/src/java/org/apache/lucene/index/DefaultIndexingChain.java
+++ b/lucene/core/src/java/org/apache/lucene/index/DefaultIndexingChain.java
@@ -313,10 +313,7 @@ final class DefaultIndexingChain extends DocConsumer {
 
   @Override
   public void abort() {
-    try {
-      storedFieldsConsumer.abort();
-    } catch (Throwable t) {
-    }
+    storedFieldsConsumer.abort();
 
     try {
       // E.g. close any open files in the term vectors writer:

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1acd2ee2/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java b/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
index 7f0b97c..0fc2e24 100644
--- a/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
+++ b/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java
@@ -1034,17 +1034,17 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
 
   /** Confirms that the incoming index sort (if any) matches the existing index sort (if any).
    *  This is unfortunately just best effort, because it could be the old index only has unsorted flushed segments built
-   *  before {@link Version#LUCENE_7_0_0} (flushed segments are sorted in Lucene 7.0).  */
-  private void validateIndexSort() {
+   *  before {@link Version#LUCENE_6_5_0} (flushed segments are sorted in Lucene 7.0).  */
+  private void validateIndexSort() throws CorruptIndexException {
     Sort indexSort = config.getIndexSort();
     if (indexSort != null) {
       for(SegmentCommitInfo info : segmentInfos) {
         Sort segmentIndexSort = info.info.getIndexSort();
         if (segmentIndexSort != null && indexSort.equals(segmentIndexSort) == false) {
           throw new IllegalArgumentException("cannot change previous indexSort=" + segmentIndexSort + " (from segment=" + info + ") to new indexSort=" + indexSort);
-        } else if (segmentIndexSort == null) {
-          // Flushed segments are not sorted if they were built with a version prior to 7.0
-          assert info.info.getVersion().onOrAfter(Version.LUCENE_7_0_0) == false;
+        } else if (segmentIndexSort == null && info.info.getVersion().onOrAfter(Version.LUCENE_6_5_0)) {
+          // Flushed segments are not sorted if they were built with a version prior to 6.5.0
+          throw new CorruptIndexException("segment not sorted with indexSort=" + segmentIndexSort, info.info.toString());
         }
       }
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1acd2ee2/lucene/core/src/java/org/apache/lucene/index/SortingLeafReader.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/SortingLeafReader.java b/lucene/core/src/java/org/apache/lucene/index/SortingLeafReader.java
index b36b284..f24a4d0 100644
--- a/lucene/core/src/java/org/apache/lucene/index/SortingLeafReader.java
+++ b/lucene/core/src/java/org/apache/lucene/index/SortingLeafReader.java
@@ -42,7 +42,7 @@ import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS;
 
 /**
  * An {@link org.apache.lucene.index.LeafReader} which supports sorting documents by a given
- * {@link Sort}. This is package private and is only used by Lucene fo BWC when it needs to merge
+ * {@link Sort}. This is package private and is only used by Lucene for BWC when it needs to merge
  * an unsorted flushed segment built by an older version (newly flushed segments are sorted since version 7.0).
  *
  * @lucene.experimental

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1acd2ee2/lucene/test-framework/src/java/org/apache/lucene/codecs/asserting/AssertingLiveDocsFormat.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/codecs/asserting/AssertingLiveDocsFormat.java b/lucene/test-framework/src/java/org/apache/lucene/codecs/asserting/AssertingLiveDocsFormat.java
index afc80d5..f4abb54 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/codecs/asserting/AssertingLiveDocsFormat.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/codecs/asserting/AssertingLiveDocsFormat.java
@@ -68,13 +68,8 @@ public class AssertingLiveDocsFormat extends LiveDocsFormat {
 
   @Override
   public void writeLiveDocs(MutableBits bits, Directory dir, SegmentCommitInfo info, int newDelCount, IOContext context) throws IOException {
-    MutableBits raw = bits;
-    /**
-     * bits is not necessarily an AssertingMutableBits because index sorting needs to wrap it in a sorted view.
-     */
-    if (bits instanceof AssertingMutableBits) {
-      raw = (MutableBits) ((AssertingMutableBits) bits).in;
-    }
+    assert bits instanceof AssertingMutableBits;
+    MutableBits raw = (MutableBits) ((AssertingMutableBits)bits).in;
     check(raw, info.info.maxDoc(), info.getDelCount() + newDelCount);
     in.writeLiveDocs(raw, dir, info, newDelCount, context);
   }


[23/38] lucene-solr:jira/solr-9857: LUCENE-7644: FieldComparatorSource.newComparator() doesn't need to throw IOException

Posted by ab...@apache.org.
LUCENE-7644: FieldComparatorSource.newComparator() doesn't need to throw IOException

This allos us to also remove the throws clause on SortField.getComparator(),
TopDocs.merge() and various Collector constructors


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

Branch: refs/heads/jira/solr-9857
Commit: 8c2ef3bc7fbebe8105c2646c81489aa9393ad402
Parents: 68d246d
Author: Alan Woodward <ro...@apache.org>
Authored: Wed Jan 18 15:16:06 2017 +0000
Committer: Alan Woodward <ro...@apache.org>
Committed: Wed Jan 18 19:17:19 2017 +0000

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   3 +
 .../lucene/search/DoubleValuesSource.java       |   2 +-
 .../lucene/search/FieldComparatorSource.java    |   8 +-
 .../lucene/search/FieldValueHitQueue.java       |  11 +-
 .../apache/lucene/search/LongValuesSource.java  |   2 +-
 .../org/apache/lucene/search/SortField.java     |   2 +-
 .../lucene/search/SortedNumericSortField.java   |   2 +-
 .../lucene/search/SortedSetSortField.java       |   2 +-
 .../java/org/apache/lucene/search/TopDocs.java  |  14 +-
 .../apache/lucene/search/TopFieldCollector.java |   4 +-
 .../lucene/search/TestElevationComparator.java  |  22 ++-
 .../search/grouping/BlockGroupingCollector.java |   2 +-
 .../grouping/FirstPassGroupingCollector.java    |   3 +-
 .../lucene/search/grouping/SearchGroup.java     |   8 +-
 .../lucene/search/grouping/TopGroups.java       |   5 +-
 .../search/join/ToParentBlockJoinCollector.java |   2 +-
 .../search/join/ToParentBlockJoinSortField.java |   2 +-
 .../lucene/queries/function/ValueSource.java    |   2 +-
 .../lucene/document/LatLonPointSortField.java   |   6 +-
 .../spatial3d/Geo3DPointOutsideSortField.java   |   5 +-
 .../lucene/spatial3d/Geo3DPointSortField.java   |   5 +-
 .../component/QueryElevationComponent.java      |  60 ++++---
 .../component/ShardFieldSortedHitQueue.java     |   8 +-
 .../solr/search/CollapsingQParserPlugin.java    |   2 +-
 .../SearchGroupShardResponseProcessor.java      | 160 +++++++++----------
 .../TopGroupsShardResponseProcessor.java        | 114 +++++++------
 .../apache/solr/schema/SortableBinaryField.java |   3 +-
 27 files changed, 214 insertions(+), 245 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index cee0335..9d1cbb7 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -71,6 +71,9 @@ API Changes
 * LUCENE-7637: TermInSetQuery requires that all terms come from the same field.
   (Adrien Grand)
 
+* LUCENE-7644: FieldComparatorSource.newComparator() and
+  SortField.getComparator() no longer throw IOException (Alan Woodward)
+
 New Features
 
 * LUCENE-7623: Add FunctionScoreQuery and FunctionMatchQuery (Alan Woodward,

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java
index af24e1a..c22a3c3 100644
--- a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java
+++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java
@@ -343,7 +343,7 @@ public abstract class DoubleValuesSource {
 
     @Override
     public FieldComparator<Double> newComparator(String fieldname, int numHits,
-                                               int sortPos, boolean reversed) throws IOException {
+                                               int sortPos, boolean reversed) {
       return new FieldComparator.DoubleComparator(numHits, fieldname, 0.0){
 
         LeafReaderContext ctx;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/core/src/java/org/apache/lucene/search/FieldComparatorSource.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/FieldComparatorSource.java b/lucene/core/src/java/org/apache/lucene/search/FieldComparatorSource.java
index 295ec9c..e7db0ba 100644
--- a/lucene/core/src/java/org/apache/lucene/search/FieldComparatorSource.java
+++ b/lucene/core/src/java/org/apache/lucene/search/FieldComparatorSource.java
@@ -17,8 +17,6 @@
 package org.apache.lucene.search;
 
 
-import java.io.IOException;
-
 /**
  * Provides a {@link FieldComparator} for custom field sorting.
  *
@@ -33,9 +31,7 @@ public abstract class FieldComparatorSource {
    * @param fieldname
    *          Name of the field to create comparator for.
    * @return FieldComparator.
-   * @throws IOException
-   *           If an error occurs reading the index.
    */
-  public abstract FieldComparator<?> newComparator(String fieldname, int numHits, int sortPos, boolean reversed)
-      throws IOException;
+  public abstract FieldComparator<?> newComparator(String fieldname, int numHits, int sortPos, boolean reversed);
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/core/src/java/org/apache/lucene/search/FieldValueHitQueue.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/FieldValueHitQueue.java b/lucene/core/src/java/org/apache/lucene/search/FieldValueHitQueue.java
index c53774c..bd1967b 100644
--- a/lucene/core/src/java/org/apache/lucene/search/FieldValueHitQueue.java
+++ b/lucene/core/src/java/org/apache/lucene/search/FieldValueHitQueue.java
@@ -58,8 +58,7 @@ public abstract class FieldValueHitQueue<T extends FieldValueHitQueue.Entry> ext
     private final int oneReverseMul;
     private final FieldComparator<?> oneComparator;
     
-    public OneComparatorFieldValueHitQueue(SortField[] fields, int size)
-        throws IOException {
+    public OneComparatorFieldValueHitQueue(SortField[] fields, int size) {
       super(fields, size);
 
       assert fields.length == 1;
@@ -96,8 +95,7 @@ public abstract class FieldValueHitQueue<T extends FieldValueHitQueue.Entry> ext
    */
   private static final class MultiComparatorsFieldValueHitQueue<T extends FieldValueHitQueue.Entry> extends FieldValueHitQueue<T> {
 
-    public MultiComparatorsFieldValueHitQueue(SortField[] fields, int size)
-        throws IOException {
+    public MultiComparatorsFieldValueHitQueue(SortField[] fields, int size) {
       super(fields, size);
     }
   
@@ -123,7 +121,7 @@ public abstract class FieldValueHitQueue<T extends FieldValueHitQueue.Entry> ext
   }
   
   // prevent instantiation and extension.
-  private FieldValueHitQueue(SortField[] fields, int size) throws IOException {
+  private FieldValueHitQueue(SortField[] fields, int size) {
     super(size);
     // When we get here, fields.length is guaranteed to be > 0, therefore no
     // need to check it again.
@@ -154,9 +152,8 @@ public abstract class FieldValueHitQueue<T extends FieldValueHitQueue.Entry> ext
    *          priority first); cannot be <code>null</code> or empty
    * @param size
    *          The number of hits to retain. Must be greater than zero.
-   * @throws IOException if there is a low-level IO error
    */
-  public static <T extends FieldValueHitQueue.Entry> FieldValueHitQueue<T> create(SortField[] fields, int size) throws IOException {
+  public static <T extends FieldValueHitQueue.Entry> FieldValueHitQueue<T> create(SortField[] fields, int size) {
 
     if (fields.length == 0) {
       throw new IllegalArgumentException("Sort must contain at least one field");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java
index 524822c..9a23cab 100644
--- a/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java
+++ b/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java
@@ -172,7 +172,7 @@ public abstract class LongValuesSource {
 
     @Override
     public FieldComparator<Long> newComparator(String fieldname, int numHits,
-                                                 int sortPos, boolean reversed) throws IOException {
+                                                 int sortPos, boolean reversed) {
       return new FieldComparator.LongComparator(numHits, fieldname, 0L){
 
         LeafReaderContext ctx;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/core/src/java/org/apache/lucene/search/SortField.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/SortField.java b/lucene/core/src/java/org/apache/lucene/search/SortField.java
index 412a50a..2cfae46 100644
--- a/lucene/core/src/java/org/apache/lucene/search/SortField.java
+++ b/lucene/core/src/java/org/apache/lucene/search/SortField.java
@@ -335,7 +335,7 @@ public class SortField {
    *   optimize themselves when they are the primary sort.
    * @return {@link FieldComparator} to use when sorting
    */
-  public FieldComparator<?> getComparator(final int numHits, final int sortPos) throws IOException {
+  public FieldComparator<?> getComparator(final int numHits, final int sortPos) {
 
     switch (type) {
     case SCORE:

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/core/src/java/org/apache/lucene/search/SortedNumericSortField.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/SortedNumericSortField.java b/lucene/core/src/java/org/apache/lucene/search/SortedNumericSortField.java
index 5b1492d..fff000b 100644
--- a/lucene/core/src/java/org/apache/lucene/search/SortedNumericSortField.java
+++ b/lucene/core/src/java/org/apache/lucene/search/SortedNumericSortField.java
@@ -136,7 +136,7 @@ public class SortedNumericSortField extends SortField {
   }
   
   @Override
-  public FieldComparator<?> getComparator(int numHits, int sortPos) throws IOException {
+  public FieldComparator<?> getComparator(int numHits, int sortPos) {
     switch(type) {
       case INT:
         return new FieldComparator.IntComparator(numHits, getField(), (Integer) missingValue) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/core/src/java/org/apache/lucene/search/SortedSetSortField.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/SortedSetSortField.java b/lucene/core/src/java/org/apache/lucene/search/SortedSetSortField.java
index da2546f..b095c6e 100644
--- a/lucene/core/src/java/org/apache/lucene/search/SortedSetSortField.java
+++ b/lucene/core/src/java/org/apache/lucene/search/SortedSetSortField.java
@@ -118,7 +118,7 @@ public class SortedSetSortField extends SortField {
   }
   
   @Override
-  public FieldComparator<?> getComparator(int numHits, int sortPos) throws IOException {
+  public FieldComparator<?> getComparator(int numHits, int sortPos) {
     return new FieldComparator.TermOrdValComparator(numHits, getField(), missingValue == STRING_LAST) {
       @Override
       protected SortedDocValues getSortedDocValues(LeafReaderContext context, String field) throws IOException {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/core/src/java/org/apache/lucene/search/TopDocs.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/TopDocs.java b/lucene/core/src/java/org/apache/lucene/search/TopDocs.java
index 69fa3c6..c1f825e 100644
--- a/lucene/core/src/java/org/apache/lucene/search/TopDocs.java
+++ b/lucene/core/src/java/org/apache/lucene/search/TopDocs.java
@@ -19,8 +19,6 @@ package org.apache.lucene.search;
 
 import org.apache.lucene.util.PriorityQueue;
 
-import java.io.IOException;
-
 /** Represents hits returned by {@link
  * IndexSearcher#search(Query,int)}. */
 public class TopDocs {
@@ -123,7 +121,7 @@ public class TopDocs {
     final FieldComparator<?>[] comparators;
     final int[] reverseMul;
 
-    public MergeSortQueue(Sort sort, TopDocs[] shardHits) throws IOException {
+    public MergeSortQueue(Sort sort, TopDocs[] shardHits) {
       super(shardHits.length);
       this.shardHits = new ScoreDoc[shardHits.length][];
       for(int shardIDX=0;shardIDX<shardHits.length;shardIDX++) {
@@ -196,7 +194,7 @@ public class TopDocs {
    *  the provided TopDocs, sorting by score. Each {@link TopDocs}
    *  instance must be sorted.
    *  @lucene.experimental */
-  public static TopDocs merge(int topN, TopDocs[] shardHits) throws IOException {
+  public static TopDocs merge(int topN, TopDocs[] shardHits) {
     return merge(0, topN, shardHits);
   }
 
@@ -205,7 +203,7 @@ public class TopDocs {
    * {@code start} top docs. This is typically useful for pagination.
    * @lucene.experimental
    */
-  public static TopDocs merge(int start, int topN, TopDocs[] shardHits) throws IOException {
+  public static TopDocs merge(int start, int topN, TopDocs[] shardHits) {
     return mergeAux(null, start, topN, shardHits);
   }
 
@@ -216,7 +214,7 @@ public class TopDocs {
    *  filled (ie, <code>fillFields=true</code> must be
    *  passed to {@link TopFieldCollector#create}).
    * @lucene.experimental */
-  public static TopFieldDocs merge(Sort sort, int topN, TopFieldDocs[] shardHits) throws IOException {
+  public static TopFieldDocs merge(Sort sort, int topN, TopFieldDocs[] shardHits) {
     return merge(sort, 0, topN, shardHits);
   }
 
@@ -225,7 +223,7 @@ public class TopDocs {
    * {@code start} top docs. This is typically useful for pagination.
    * @lucene.experimental
    */
-  public static TopFieldDocs merge(Sort sort, int start, int topN, TopFieldDocs[] shardHits) throws IOException {
+  public static TopFieldDocs merge(Sort sort, int start, int topN, TopFieldDocs[] shardHits) {
     if (sort == null) {
       throw new IllegalArgumentException("sort must be non-null when merging field-docs");
     }
@@ -234,7 +232,7 @@ public class TopDocs {
 
   /** Auxiliary method used by the {@link #merge} impls. A sort value of null
    *  is used to indicate that docs should be sorted by score. */
-  private static TopDocs mergeAux(Sort sort, int start, int size, TopDocs[] shardHits) throws IOException {
+  private static TopDocs mergeAux(Sort sort, int start, int size, TopDocs[] shardHits) {
     final PriorityQueue<ShardRef> queue;
     if (sort == null) {
       queue = new ScoreMergeSortQueue(shardHits);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java b/lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java
index c7274d5..3433906 100644
--- a/lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java
+++ b/lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java
@@ -475,11 +475,9 @@ public abstract class TopFieldCollector extends TopDocsCollector<Entry> {
    *          <code>trackDocScores</code> to true as well.
    * @return a {@link TopFieldCollector} instance which will sort the results by
    *         the sort criteria.
-   * @throws IOException if there is a low-level I/O error
    */
   public static TopFieldCollector create(Sort sort, int numHits, FieldDoc after,
-      boolean fillFields, boolean trackDocScores, boolean trackMaxScore)
-      throws IOException {
+      boolean fillFields, boolean trackDocScores, boolean trackMaxScore) {
 
     if (sort.fields.length == 0) {
       throw new IllegalArgumentException("Sort must contain at least one field");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/core/src/test/org/apache/lucene/search/TestElevationComparator.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestElevationComparator.java b/lucene/core/src/test/org/apache/lucene/search/TestElevationComparator.java
index 9ca2302..fb01e1d 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestElevationComparator.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestElevationComparator.java
@@ -17,20 +17,26 @@
 package org.apache.lucene.search;
 
 
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.apache.lucene.analysis.MockAnalyzer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.SortedDocValuesField;
-import org.apache.lucene.index.*;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.SortedDocValues;
+import org.apache.lucene.index.Term;
 import org.apache.lucene.search.FieldValueHitQueue.Entry;
 import org.apache.lucene.search.similarities.ClassicSimilarity;
-import org.apache.lucene.store.*;
-import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.BytesRef;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
+import org.apache.lucene.util.LuceneTestCase;
 
 public class TestElevationComparator extends LuceneTestCase {
 
@@ -144,7 +150,7 @@ class ElevationComparatorSource extends FieldComparatorSource {
   }
 
   @Override
-  public FieldComparator<Integer> newComparator(final String fieldname, final int numHits, int sortPos, boolean reversed) throws IOException {
+  public FieldComparator<Integer> newComparator(final String fieldname, final int numHits, int sortPos, boolean reversed) {
    return new FieldComparator<Integer>() {
 
      private final int[] values = new int[numHits];

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/grouping/src/java/org/apache/lucene/search/grouping/BlockGroupingCollector.java
----------------------------------------------------------------------
diff --git a/lucene/grouping/src/java/org/apache/lucene/search/grouping/BlockGroupingCollector.java b/lucene/grouping/src/java/org/apache/lucene/search/grouping/BlockGroupingCollector.java
index 8d1781e..c965042 100644
--- a/lucene/grouping/src/java/org/apache/lucene/search/grouping/BlockGroupingCollector.java
+++ b/lucene/grouping/src/java/org/apache/lucene/search/grouping/BlockGroupingCollector.java
@@ -216,7 +216,7 @@ public class BlockGroupingCollector extends SimpleCollector {
    *  @param lastDocPerGroup a {@link Weight} that marks the
    *    last document in each group.
    */
-  public BlockGroupingCollector(Sort groupSort, int topNGroups, boolean needsScores, Weight lastDocPerGroup) throws IOException {
+  public BlockGroupingCollector(Sort groupSort, int topNGroups, boolean needsScores, Weight lastDocPerGroup) {
 
     if (topNGroups < 1) {
       throw new IllegalArgumentException("topNGroups must be >= 1 (got " + topNGroups + ")");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/grouping/src/java/org/apache/lucene/search/grouping/FirstPassGroupingCollector.java
----------------------------------------------------------------------
diff --git a/lucene/grouping/src/java/org/apache/lucene/search/grouping/FirstPassGroupingCollector.java b/lucene/grouping/src/java/org/apache/lucene/search/grouping/FirstPassGroupingCollector.java
index ef47f96..02bb1a2 100644
--- a/lucene/grouping/src/java/org/apache/lucene/search/grouping/FirstPassGroupingCollector.java
+++ b/lucene/grouping/src/java/org/apache/lucene/search/grouping/FirstPassGroupingCollector.java
@@ -67,10 +67,9 @@ abstract public class FirstPassGroupingCollector<T> extends SimpleCollector {
    *    ie, if you want to groupSort by relevance use
    *    Sort.RELEVANCE.
    *  @param topNGroups How many top groups to keep.
-   *  @throws IOException If I/O related errors occur
    */
   @SuppressWarnings({"unchecked", "rawtypes"})
-  public FirstPassGroupingCollector(Sort groupSort, int topNGroups) throws IOException {
+  public FirstPassGroupingCollector(Sort groupSort, int topNGroups) {
     if (topNGroups < 1) {
       throw new IllegalArgumentException("topNGroups must be >= 1 (got " + topNGroups + ")");
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/grouping/src/java/org/apache/lucene/search/grouping/SearchGroup.java
----------------------------------------------------------------------
diff --git a/lucene/grouping/src/java/org/apache/lucene/search/grouping/SearchGroup.java b/lucene/grouping/src/java/org/apache/lucene/search/grouping/SearchGroup.java
index 95a507c..58e1f74 100644
--- a/lucene/grouping/src/java/org/apache/lucene/search/grouping/SearchGroup.java
+++ b/lucene/grouping/src/java/org/apache/lucene/search/grouping/SearchGroup.java
@@ -16,7 +16,6 @@
  */
 package org.apache.lucene.search.grouping;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -167,7 +166,7 @@ public class SearchGroup<T> {
     public final int[] reversed;
 
     @SuppressWarnings({"unchecked", "rawtypes"})
-    public GroupComparator(Sort groupSort) throws IOException {
+    public GroupComparator(Sort groupSort) {
       final SortField[] sortFields = groupSort.getSort();
       comparators = new FieldComparator[sortFields.length];
       reversed = new int[sortFields.length];
@@ -208,7 +207,7 @@ public class SearchGroup<T> {
     private final NavigableSet<MergedGroup<T>> queue;
     private final Map<T,MergedGroup<T>> groupsSeen;
 
-    public GroupMerger(Sort groupSort) throws IOException {
+    public GroupMerger(Sort groupSort) {
       groupComp = new GroupComparator<>(groupSort);
       queue = new TreeSet<>(groupComp);
       groupsSeen = new HashMap<>();
@@ -340,8 +339,7 @@ public class SearchGroup<T> {
    *
    * <p>NOTE: this returns null if the topGroups is empty.
    */
-  public static <T> Collection<SearchGroup<T>> merge(List<Collection<SearchGroup<T>>> topGroups, int offset, int topN, Sort groupSort)
-    throws IOException {
+  public static <T> Collection<SearchGroup<T>> merge(List<Collection<SearchGroup<T>>> topGroups, int offset, int topN, Sort groupSort) {
     if (topGroups.isEmpty()) {
       return null;
     } else {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/grouping/src/java/org/apache/lucene/search/grouping/TopGroups.java
----------------------------------------------------------------------
diff --git a/lucene/grouping/src/java/org/apache/lucene/search/grouping/TopGroups.java b/lucene/grouping/src/java/org/apache/lucene/search/grouping/TopGroups.java
index 803482b..36ab8d9 100644
--- a/lucene/grouping/src/java/org/apache/lucene/search/grouping/TopGroups.java
+++ b/lucene/grouping/src/java/org/apache/lucene/search/grouping/TopGroups.java
@@ -16,8 +16,6 @@
  */
 package org.apache.lucene.search.grouping;
 
-import java.io.IOException;
-
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
@@ -97,8 +95,7 @@ public class TopGroups<T> {
    * <b>NOTE</b>: the topDocs in each GroupDocs is actually
    * an instance of TopDocsAndShards
    */
-  public static <T> TopGroups<T> merge(TopGroups<T>[] shardGroups, Sort groupSort, Sort docSort, int docOffset, int docTopN, ScoreMergeMode scoreMergeMode)
-    throws IOException {
+  public static <T> TopGroups<T> merge(TopGroups<T>[] shardGroups, Sort groupSort, Sort docSort, int docOffset, int docTopN, ScoreMergeMode scoreMergeMode) {
 
     //System.out.println("TopGroups.merge");
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java
index 70e1549..f81b943 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java
@@ -116,7 +116,7 @@ public class ToParentBlockJoinCollector implements Collector {
    *  not be null.  If you pass true trackScores, all
    *  ToParentBlockQuery instances must not use
    *  ScoreMode.None. */
-  public ToParentBlockJoinCollector(Sort sort, int numParentHits, boolean trackScores, boolean trackMaxScore) throws IOException {
+  public ToParentBlockJoinCollector(Sort sort, int numParentHits, boolean trackScores, boolean trackMaxScore) {
     // TODO: allow null sort to be specialized to relevance
     // only collector
     this.sort = sort;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinSortField.java
----------------------------------------------------------------------
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinSortField.java b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinSortField.java
index 1b82c0c..c757086 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinSortField.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinSortField.java
@@ -87,7 +87,7 @@ public class ToParentBlockJoinSortField extends SortField {
   }
 
   @Override
-  public FieldComparator<?> getComparator(int numHits, int sortPos) throws IOException {
+  public FieldComparator<?> getComparator(int numHits, int sortPos) {
     switch (getType()) {
       case STRING:
         return getStringComparator(numHits);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java
index 5bf6324..6bf2926 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java
@@ -229,7 +229,7 @@ public abstract class ValueSource {
 
     @Override
     public FieldComparator<Double> newComparator(String fieldname, int numHits,
-                                         int sortPos, boolean reversed) throws IOException {
+                                         int sortPos, boolean reversed) {
       return new ValueSourceComparator(context, numHits);
     }
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java
index c886438..10e72cc 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java
@@ -16,11 +16,9 @@
  */
 package org.apache.lucene.document;
 
-import java.io.IOException;
-
+import org.apache.lucene.geo.GeoUtils;
 import org.apache.lucene.search.FieldComparator;
 import org.apache.lucene.search.SortField;
-import org.apache.lucene.geo.GeoUtils;
 
 /**
  * Sorts by distance from an origin location.
@@ -42,7 +40,7 @@ final class LatLonPointSortField extends SortField {
   }
   
   @Override
-  public FieldComparator<?> getComparator(int numHits, int sortPos) throws IOException {
+  public FieldComparator<?> getComparator(int numHits, int sortPos) {
     return new LatLonPointDistanceComparator(getField(), latitude, longitude, numHits);
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointOutsideSortField.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointOutsideSortField.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointOutsideSortField.java
index b48984c..3f37b22 100644
--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointOutsideSortField.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointOutsideSortField.java
@@ -16,11 +16,8 @@
  */
 package org.apache.lucene.spatial3d;
 
-import java.io.IOException;
-
 import org.apache.lucene.search.FieldComparator;
 import org.apache.lucene.search.SortField;
-
 import org.apache.lucene.spatial3d.geom.GeoOutsideDistance;
 
 /**
@@ -42,7 +39,7 @@ final class Geo3DPointOutsideSortField extends SortField {
   }
   
   @Override
-  public FieldComparator<?> getComparator(int numHits, int sortPos) throws IOException {
+  public FieldComparator<?> getComparator(int numHits, int sortPos) {
     return new Geo3DPointOutsideDistanceComparator(getField(), distanceShape, numHits);
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointSortField.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointSortField.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointSortField.java
index 4d6b417..bf1de77 100644
--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointSortField.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointSortField.java
@@ -16,11 +16,8 @@
  */
 package org.apache.lucene.spatial3d;
 
-import java.io.IOException;
-
 import org.apache.lucene.search.FieldComparator;
 import org.apache.lucene.search.SortField;
-
 import org.apache.lucene.spatial3d.geom.GeoDistanceShape;
 
 /**
@@ -42,7 +39,7 @@ final class Geo3DPointSortField extends SortField {
   }
   
   @Override
-  public FieldComparator<?> getComparator(int numHits, int sortPos) throws IOException {
+  public FieldComparator<?> getComparator(int numHits, int sortPos) {
     return new Geo3DPointDistanceComparator(getField(), distanceShape, numHits);
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java b/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
index 25157cf..8482d65 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java
@@ -16,14 +16,35 @@
  */
 package org.apache.solr.handler.component;
 
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import com.carrotsearch.hppc.IntIntHashMap;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
+import org.apache.lucene.index.Fields;
+import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.LeafReader;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.PostingsEnum;
-import org.apache.lucene.index.Fields;
-import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.Terms;
 import org.apache.lucene.index.TermsEnum;
@@ -46,22 +67,22 @@ import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.QueryElevationParams;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.common.util.StrUtils;
-import org.apache.solr.schema.IndexSchema;
-import org.apache.solr.search.QueryParsing;
-import org.apache.solr.search.grouping.GroupingSpecification;
-import org.apache.solr.util.DOMUtil;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.core.Config;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.transform.ElevatedMarkerFactory;
 import org.apache.solr.response.transform.ExcludedMarkerFactory;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.QueryParsing;
 import org.apache.solr.search.SolrIndexSearcher;
 import org.apache.solr.search.SortSpec;
+import org.apache.solr.search.grouping.GroupingSpecification;
+import org.apache.solr.util.DOMUtil;
 import org.apache.solr.util.RefCounted;
 import org.apache.solr.util.VersionedFile;
 import org.apache.solr.util.plugin.SolrCoreAware;
@@ -71,29 +92,6 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.InputSource;
 
-import com.carrotsearch.hppc.IntIntHashMap;
-
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.invoke.MethodHandles;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.WeakHashMap;
-
 /**
  * A component to elevate some documents to the top of the result set.
  *
@@ -628,7 +626,7 @@ public class QueryElevationComponent extends SearchComponent implements SolrCore
   }
 
   @Override
-  public FieldComparator<Integer> newComparator(String fieldname, final int numHits, int sortPos, boolean reversed) throws IOException {
+  public FieldComparator<Integer> newComparator(String fieldname, final int numHits, int sortPos, boolean reversed) {
     return new SimpleFieldComparator<Integer>() {
       private final int[] values = new int[numHits];
       private int bottomVal;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/solr/core/src/java/org/apache/solr/handler/component/ShardFieldSortedHitQueue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/ShardFieldSortedHitQueue.java b/solr/core/src/java/org/apache/solr/handler/component/ShardFieldSortedHitQueue.java
index 81aaf66..ef0e624 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/ShardFieldSortedHitQueue.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/ShardFieldSortedHitQueue.java
@@ -149,13 +149,7 @@ public class ShardFieldSortedHitQueue extends PriorityQueue<ShardDoc> {
   }
 
   Comparator<ShardDoc> comparatorFieldComparator(SortField sortField) {
-    final FieldComparator fieldComparator;
-    try {
-      fieldComparator = sortField.getComparator(0, 0);
-    } catch (IOException e) {
-      throw new RuntimeException("Unable to get FieldComparator for sortField " + sortField);
-    }
-
+    final FieldComparator fieldComparator = sortField.getComparator(0, 0);
     return new ShardComparator(sortField) {
       // Since the PriorityQueue keeps the biggest elements by default,
       // we need to reverse the field compare ordering so that the

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
index 44aade5..65d470e 100644
--- a/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
@@ -2652,7 +2652,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
      * Constructs an instance based on the the (raw, un-rewritten) SortFields to be used, 
      * and an initial number of expected groups (will grow as needed).
      */
-    public SortFieldsCompare(SortField[] sorts, int initNumGroups) throws IOException {
+    public SortFieldsCompare(SortField[] sorts, int initNumGroups) {
       this.sorts = sorts;
       numClauses = sorts.length;
       fieldComparators = new FieldComparator[numClauses];

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java b/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java
index 0acd6f9..1645b1e 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java
@@ -16,11 +16,20 @@
  */
 package org.apache.solr.search.grouping.distributed.responseprocessor;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.grouping.SearchGroup;
 import org.apache.lucene.util.BytesRef;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ShardParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
@@ -33,11 +42,6 @@ import org.apache.solr.search.grouping.distributed.ShardResponseProcessor;
 import org.apache.solr.search.grouping.distributed.command.SearchGroupsFieldCommandResult;
 import org.apache.solr.search.grouping.distributed.shardresultserializer.SearchGroupsResultTransformer;
 
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.*;
-
 /**
  * Concrete implementation for merging {@link SearchGroup} instances from shard responses.
  */
@@ -65,94 +69,90 @@ public class SearchGroupShardResponseProcessor implements ShardResponseProcessor
     }
 
     SearchGroupsResultTransformer serializer = new SearchGroupsResultTransformer(rb.req.getSearcher());
-    try {
-      int maxElapsedTime = 0;
-      int hitCountDuringFirstPhase = 0;
+    int maxElapsedTime = 0;
+    int hitCountDuringFirstPhase = 0;
 
-      NamedList<Object> shardInfo = null;
-      if (rb.req.getParams().getBool(ShardParams.SHARDS_INFO, false)) {
-        shardInfo = new SimpleOrderedMap<>(shardRequest.responses.size());
-        rb.rsp.getValues().add(ShardParams.SHARDS_INFO + ".firstPhase", shardInfo);
-      }
+    NamedList<Object> shardInfo = null;
+    if (rb.req.getParams().getBool(ShardParams.SHARDS_INFO, false)) {
+      shardInfo = new SimpleOrderedMap<>(shardRequest.responses.size());
+      rb.rsp.getValues().add(ShardParams.SHARDS_INFO + ".firstPhase", shardInfo);
+    }
 
-      for (ShardResponse srsp : shardRequest.responses) {
-        if (shardInfo != null) {
-          SimpleOrderedMap<Object> nl = new SimpleOrderedMap<>(4);
+    for (ShardResponse srsp : shardRequest.responses) {
+      if (shardInfo != null) {
+        SimpleOrderedMap<Object> nl = new SimpleOrderedMap<>(4);
 
-          if (srsp.getException() != null) {
-            Throwable t = srsp.getException();
-            if (t instanceof SolrServerException) {
-              t = ((SolrServerException) t).getCause();
-            }
-            nl.add("error", t.toString());
-            StringWriter trace = new StringWriter();
-            t.printStackTrace(new PrintWriter(trace));
-            nl.add("trace", trace.toString());
-          } else {
-            nl.add("numFound", (Integer) srsp.getSolrResponse().getResponse().get("totalHitCount"));
-          }
-          if (srsp.getSolrResponse() != null) {
-            nl.add("time", srsp.getSolrResponse().getElapsedTime());
-          }
-          if (srsp.getShardAddress() != null) {
-            nl.add("shardAddress", srsp.getShardAddress());
+        if (srsp.getException() != null) {
+          Throwable t = srsp.getException();
+          if (t instanceof SolrServerException) {
+            t = ((SolrServerException) t).getCause();
           }
-          shardInfo.add(srsp.getShard(), nl);
+          nl.add("error", t.toString());
+          StringWriter trace = new StringWriter();
+          t.printStackTrace(new PrintWriter(trace));
+          nl.add("trace", trace.toString());
+        } else {
+          nl.add("numFound", (Integer) srsp.getSolrResponse().getResponse().get("totalHitCount"));
         }
-        if (rb.req.getParams().getBool(ShardParams.SHARDS_TOLERANT, false) && srsp.getException() != null) {
-          if(rb.rsp.getResponseHeader().get(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY) == null) {
-            rb.rsp.getResponseHeader().add(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY, Boolean.TRUE);
-          }
-          continue; // continue if there was an error and we're tolerant.  
+        if (srsp.getSolrResponse() != null) {
+          nl.add("time", srsp.getSolrResponse().getElapsedTime());
+        }
+        if (srsp.getShardAddress() != null) {
+          nl.add("shardAddress", srsp.getShardAddress());
         }
-        maxElapsedTime = (int) Math.max(maxElapsedTime, srsp.getSolrResponse().getElapsedTime());
-        @SuppressWarnings("unchecked")
-        NamedList<NamedList> firstPhaseResult = (NamedList<NamedList>) srsp.getSolrResponse().getResponse().get("firstPhase");
-        final Map<String, SearchGroupsFieldCommandResult> result = serializer.transformToNative(firstPhaseResult, groupSort, sortWithinGroup, srsp.getShard());
-        for (String field : commandSearchGroups.keySet()) {
-          final SearchGroupsFieldCommandResult firstPhaseCommandResult = result.get(field);
+        shardInfo.add(srsp.getShard(), nl);
+      }
+      if (rb.req.getParams().getBool(ShardParams.SHARDS_TOLERANT, false) && srsp.getException() != null) {
+        if(rb.rsp.getResponseHeader().get(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY) == null) {
+          rb.rsp.getResponseHeader().add(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY, Boolean.TRUE);
+        }
+        continue; // continue if there was an error and we're tolerant.
+      }
+      maxElapsedTime = (int) Math.max(maxElapsedTime, srsp.getSolrResponse().getElapsedTime());
+      @SuppressWarnings("unchecked")
+      NamedList<NamedList> firstPhaseResult = (NamedList<NamedList>) srsp.getSolrResponse().getResponse().get("firstPhase");
+      final Map<String, SearchGroupsFieldCommandResult> result = serializer.transformToNative(firstPhaseResult, groupSort, sortWithinGroup, srsp.getShard());
+      for (String field : commandSearchGroups.keySet()) {
+        final SearchGroupsFieldCommandResult firstPhaseCommandResult = result.get(field);
 
-          final Integer groupCount = firstPhaseCommandResult.getGroupCount();
-          if (groupCount != null) {
-            Integer existingGroupCount = rb.mergedGroupCounts.get(field);
-            // Assuming groups don't cross shard boundary...
-            rb.mergedGroupCounts.put(field, existingGroupCount != null ? existingGroupCount + groupCount : groupCount);
-          }
+        final Integer groupCount = firstPhaseCommandResult.getGroupCount();
+        if (groupCount != null) {
+          Integer existingGroupCount = rb.mergedGroupCounts.get(field);
+          // Assuming groups don't cross shard boundary...
+          rb.mergedGroupCounts.put(field, existingGroupCount != null ? existingGroupCount + groupCount : groupCount);
+        }
 
-          final Collection<SearchGroup<BytesRef>> searchGroups = firstPhaseCommandResult.getSearchGroups();
-          if (searchGroups == null) {
-            continue;
-          }
+        final Collection<SearchGroup<BytesRef>> searchGroups = firstPhaseCommandResult.getSearchGroups();
+        if (searchGroups == null) {
+          continue;
+        }
 
-          commandSearchGroups.get(field).add(searchGroups);
-          for (SearchGroup<BytesRef> searchGroup : searchGroups) {
-            Map<SearchGroup<BytesRef>, java.util.Set<String>> map = tempSearchGroupToShards.get(field);
-            Set<String> shards = map.get(searchGroup);
-            if (shards == null) {
-              shards = new HashSet<>();
-              map.put(searchGroup, shards);
-            }
-            shards.add(srsp.getShard());
+        commandSearchGroups.get(field).add(searchGroups);
+        for (SearchGroup<BytesRef> searchGroup : searchGroups) {
+          Map<SearchGroup<BytesRef>, Set<String>> map = tempSearchGroupToShards.get(field);
+          Set<String> shards = map.get(searchGroup);
+          if (shards == null) {
+            shards = new HashSet<>();
+            map.put(searchGroup, shards);
           }
+          shards.add(srsp.getShard());
         }
-        hitCountDuringFirstPhase += (Integer) srsp.getSolrResponse().getResponse().get("totalHitCount");
       }
-      rb.totalHitCount = hitCountDuringFirstPhase;
-      rb.firstPhaseElapsedTime = maxElapsedTime;
-      for (String groupField : commandSearchGroups.keySet()) {
-        List<Collection<SearchGroup<BytesRef>>> topGroups = commandSearchGroups.get(groupField);
-        Collection<SearchGroup<BytesRef>> mergedTopGroups = SearchGroup.merge(topGroups, ss.getOffset(), ss.getCount(), groupSort);
-        if (mergedTopGroups == null) {
-          continue;
-        }
+      hitCountDuringFirstPhase += (Integer) srsp.getSolrResponse().getResponse().get("totalHitCount");
+    }
+    rb.totalHitCount = hitCountDuringFirstPhase;
+    rb.firstPhaseElapsedTime = maxElapsedTime;
+    for (String groupField : commandSearchGroups.keySet()) {
+      List<Collection<SearchGroup<BytesRef>>> topGroups = commandSearchGroups.get(groupField);
+      Collection<SearchGroup<BytesRef>> mergedTopGroups = SearchGroup.merge(topGroups, ss.getOffset(), ss.getCount(), groupSort);
+      if (mergedTopGroups == null) {
+        continue;
+      }
 
-        rb.mergedSearchGroups.put(groupField, mergedTopGroups);
-        for (SearchGroup<BytesRef> mergedTopGroup : mergedTopGroups) {
-          rb.searchGroupToShards.get(groupField).put(mergedTopGroup, tempSearchGroupToShards.get(groupField).get(mergedTopGroup));
-        }
+      rb.mergedSearchGroups.put(groupField, mergedTopGroups);
+      for (SearchGroup<BytesRef> mergedTopGroup : mergedTopGroups) {
+        rb.searchGroupToShards.get(groupField).put(mergedTopGroup, tempSearchGroupToShards.get(groupField).get(mergedTopGroup));
       }
-    } catch (IOException e) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/TopGroupsShardResponseProcessor.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/TopGroupsShardResponseProcessor.java b/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/TopGroupsShardResponseProcessor.java
index 7e38e5d..2ac83c6 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/TopGroupsShardResponseProcessor.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/TopGroupsShardResponseProcessor.java
@@ -16,6 +16,13 @@
  */
 package org.apache.solr.search.grouping.distributed.responseprocessor;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.TopDocs;
@@ -24,7 +31,6 @@ import org.apache.lucene.search.grouping.GroupDocs;
 import org.apache.lucene.search.grouping.TopGroups;
 import org.apache.lucene.util.BytesRef;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ShardParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
@@ -38,14 +44,6 @@ import org.apache.solr.search.grouping.distributed.ShardResponseProcessor;
 import org.apache.solr.search.grouping.distributed.command.QueryCommandResult;
 import org.apache.solr.search.grouping.distributed.shardresultserializer.TopGroupsResultTransformer;
 
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
 /**
  * Concrete implementation for merging {@link TopGroups} instances from shard responses.
  */
@@ -152,68 +150,64 @@ public class TopGroupsShardResponseProcessor implements ShardResponseProcessor {
         individualShardInfo.add("maxScore", maxScore);
       }
     }
-    try {
-      for (String groupField : commandTopGroups.keySet()) {
-        List<TopGroups<BytesRef>> topGroups = commandTopGroups.get(groupField);
-        if (topGroups.isEmpty()) {
-          continue;
-        }
+    for (String groupField : commandTopGroups.keySet()) {
+      List<TopGroups<BytesRef>> topGroups = commandTopGroups.get(groupField);
+      if (topGroups.isEmpty()) {
+        continue;
+      }
 
-        TopGroups<BytesRef>[] topGroupsArr = new TopGroups[topGroups.size()];
-        int docsPerGroup = docsPerGroupDefault;
-        if (docsPerGroup < 0) {
-          docsPerGroup = 0;
-          for (TopGroups subTopGroups : topGroups) {
-            docsPerGroup += subTopGroups.totalGroupedHitCount;
-          }
+      TopGroups<BytesRef>[] topGroupsArr = new TopGroups[topGroups.size()];
+      int docsPerGroup = docsPerGroupDefault;
+      if (docsPerGroup < 0) {
+        docsPerGroup = 0;
+        for (TopGroups subTopGroups : topGroups) {
+          docsPerGroup += subTopGroups.totalGroupedHitCount;
         }
-        rb.mergedTopGroups.put(groupField, TopGroups.merge(topGroups.toArray(topGroupsArr), groupSort, sortWithinGroup, groupOffsetDefault, docsPerGroup, TopGroups.ScoreMergeMode.None));
       }
+      rb.mergedTopGroups.put(groupField, TopGroups.merge(topGroups.toArray(topGroupsArr), groupSort, sortWithinGroup, groupOffsetDefault, docsPerGroup, TopGroups.ScoreMergeMode.None));
+    }
 
-      for (String query : commandTopDocs.keySet()) {
-        List<QueryCommandResult> queryCommandResults = commandTopDocs.get(query);
-        List<TopDocs> topDocs = new ArrayList<>(queryCommandResults.size());
-        int mergedMatches = 0;
-        for (QueryCommandResult queryCommandResult : queryCommandResults) {
-          topDocs.add(queryCommandResult.getTopDocs());
-          mergedMatches += queryCommandResult.getMatches();
-        }
+    for (String query : commandTopDocs.keySet()) {
+      List<QueryCommandResult> queryCommandResults = commandTopDocs.get(query);
+      List<TopDocs> topDocs = new ArrayList<>(queryCommandResults.size());
+      int mergedMatches = 0;
+      for (QueryCommandResult queryCommandResult : queryCommandResults) {
+        topDocs.add(queryCommandResult.getTopDocs());
+        mergedMatches += queryCommandResult.getMatches();
+      }
 
-        int topN = rb.getGroupingSpec().getOffset() + rb.getGroupingSpec().getLimit();
-        final TopDocs mergedTopDocs;
-        if (sortWithinGroup.equals(Sort.RELEVANCE)) {
-          mergedTopDocs = TopDocs.merge(topN, topDocs.toArray(new TopDocs[topDocs.size()]));
-        } else {
-          mergedTopDocs = TopDocs.merge(sortWithinGroup, topN, topDocs.toArray(new TopFieldDocs[topDocs.size()]));
-        }
-        rb.mergedQueryCommandResults.put(query, new QueryCommandResult(mergedTopDocs, mergedMatches));
+      int topN = rb.getGroupingSpec().getOffset() + rb.getGroupingSpec().getLimit();
+      final TopDocs mergedTopDocs;
+      if (sortWithinGroup.equals(Sort.RELEVANCE)) {
+        mergedTopDocs = TopDocs.merge(topN, topDocs.toArray(new TopDocs[topDocs.size()]));
+      } else {
+        mergedTopDocs = TopDocs.merge(sortWithinGroup, topN, topDocs.toArray(new TopFieldDocs[topDocs.size()]));
       }
+      rb.mergedQueryCommandResults.put(query, new QueryCommandResult(mergedTopDocs, mergedMatches));
+    }
 
-      Map<Object, ShardDoc> resultIds = new HashMap<>();
-      int i = 0;
-      for (TopGroups<BytesRef> topGroups : rb.mergedTopGroups.values()) {
-        for (GroupDocs<BytesRef> group : topGroups.groups) {
-          for (ScoreDoc scoreDoc : group.scoreDocs) {
-            ShardDoc solrDoc = (ShardDoc) scoreDoc;
-            // Include the first if there are duplicate IDs
-            if ( ! resultIds.containsKey(solrDoc.id)) {
-              solrDoc.positionInResponse = i++;
-              resultIds.put(solrDoc.id, solrDoc);
-            }
+    Map<Object, ShardDoc> resultIds = new HashMap<>();
+    int i = 0;
+    for (TopGroups<BytesRef> topGroups : rb.mergedTopGroups.values()) {
+      for (GroupDocs<BytesRef> group : topGroups.groups) {
+        for (ScoreDoc scoreDoc : group.scoreDocs) {
+          ShardDoc solrDoc = (ShardDoc) scoreDoc;
+          // Include the first if there are duplicate IDs
+          if ( ! resultIds.containsKey(solrDoc.id)) {
+            solrDoc.positionInResponse = i++;
+            resultIds.put(solrDoc.id, solrDoc);
           }
         }
       }
-      for (QueryCommandResult queryCommandResult : rb.mergedQueryCommandResults.values()) {
-        for (ScoreDoc scoreDoc : queryCommandResult.getTopDocs().scoreDocs) {
-          ShardDoc solrDoc = (ShardDoc) scoreDoc;
-          solrDoc.positionInResponse = i++;
-          resultIds.put(solrDoc.id, solrDoc);
-        }
+    }
+    for (QueryCommandResult queryCommandResult : rb.mergedQueryCommandResults.values()) {
+      for (ScoreDoc scoreDoc : queryCommandResult.getTopDocs().scoreDocs) {
+        ShardDoc solrDoc = (ShardDoc) scoreDoc;
+        solrDoc.positionInResponse = i++;
+        resultIds.put(solrDoc.id, solrDoc);
       }
-
-      rb.resultIds = resultIds;
-    } catch (IOException e) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
     }
+
+    rb.resultIds = resultIds;
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8c2ef3bc/solr/core/src/test/org/apache/solr/schema/SortableBinaryField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/SortableBinaryField.java b/solr/core/src/test/org/apache/solr/schema/SortableBinaryField.java
index 5bd565b..1ebf4cc 100644
--- a/solr/core/src/test/org/apache/solr/schema/SortableBinaryField.java
+++ b/solr/core/src/test/org/apache/solr/schema/SortableBinaryField.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.schema;
 
-import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -71,7 +70,7 @@ public class SortableBinaryField extends BinaryField {
       super(field, new FieldComparatorSource() {
         @Override
         public FieldComparator.TermOrdValComparator newComparator
-            (final String fieldname, final int numHits, final int sortPos, final boolean reversed) throws IOException {
+            (final String fieldname, final int numHits, final int sortPos, final boolean reversed) {
           return new FieldComparator.TermOrdValComparator(numHits, fieldname);
         }}, reverse);
     }


[34/38] lucene-solr:jira/solr-9857: SOLR-9885: Allow pre-startup Solr log management in Solr bin scripts to be disabled.

Posted by ab...@apache.org.
SOLR-9885: Allow pre-startup Solr log management in Solr bin scripts to be disabled.


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

Branch: refs/heads/jira/solr-9857
Commit: 075aec91cd2c10e3f9a62adcf0feadc705c205ec
Parents: bb35732
Author: markrmiller <ma...@apache.org>
Authored: Thu Jan 19 03:07:09 2017 -0500
Committer: markrmiller <ma...@apache.org>
Committed: Thu Jan 19 03:07:09 2017 -0500

----------------------------------------------------------------------
 solr/CHANGES.txt     |  2 ++
 solr/bin/solr        | 11 ++++++-----
 solr/bin/solr.cmd    | 13 +++++++++----
 solr/bin/solr.in.cmd |  5 +++++
 solr/bin/solr.in.sh  |  5 +++++
 5 files changed, 27 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/075aec91/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index aab5116..c0fe505 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -102,6 +102,8 @@ New Features
 
 * SOLR-9926: Allow passing arbitrary java system properties to zkcli. (Hrishikesh Gadre via Mark Miller)
 
+* SOLR-9885: Allow pre-startup Solr log management in Solr bin scripts to be disabled. (Mano Kovacs via Mark Miller)
+
 Bug Fixes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/075aec91/solr/bin/solr
----------------------------------------------------------------------
diff --git a/solr/bin/solr b/solr/bin/solr
index fcf864b..c2d0feb 100755
--- a/solr/bin/solr
+++ b/solr/bin/solr
@@ -1480,11 +1480,12 @@ if [ ! -e "$SOLR_HOME" ]; then
   echo -e "\nSolr home directory $SOLR_HOME not found!\n"
   exit 1
 fi
-
-run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -remove_old_solr_logs 7 || echo "Failed removing old solr logs"
-run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -archive_gc_logs        || echo "Failed archiving old GC logs"
-run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -archive_console_logs   || echo "Failed archiving old console logs"
-run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -rotate_solr_logs 9     || echo "Failed rotating old solr logs"
+if [ "${SOLR_LOG_PRESTART_ROTATION:=true}" == "true" ]; then
+  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -remove_old_solr_logs 7 || echo "Failed removing old solr logs"
+  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -archive_gc_logs        || echo "Failed archiving old GC logs"
+  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -archive_console_logs   || echo "Failed archiving old console logs"
+  run_tool utils -s "$DEFAULT_SERVER_DIR" -l "$SOLR_LOGS_DIR" -rotate_solr_logs 9     || echo "Failed rotating old solr logs"
+fi
 
 java_ver_out=`echo "$("$JAVA" -version 2>&1)"`
 JAVA_VERSION=`echo $java_ver_out | grep "java version" | awk '{ print substr($3, 2, length($3)-2); }'`

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/075aec91/solr/bin/solr.cmd
----------------------------------------------------------------------
diff --git a/solr/bin/solr.cmd b/solr/bin/solr.cmd
index 04398bc..732c2de 100644
--- a/solr/bin/solr.cmd
+++ b/solr/bin/solr.cmd
@@ -939,10 +939,15 @@ IF ERRORLEVEL 1 (
 )
 
 REM Clean up and rotate logs
-call :run_utils "-remove_old_solr_logs 7" || echo "Failed removing old solr logs"
-call :run_utils "-archive_gc_logs"        || echo "Failed archiving old GC logs"
-call :run_utils "-archive_console_logs"   || echo "Failed archiving old console logs"
-call :run_utils "-rotate_solr_logs 9"     || echo "Failed rotating old solr logs"
+IF [%SOLR_LOG_PRESTART_ROTATION%] == [] (
+  set SOLR_LOG_PRESTART_ROTATION=true
+)
+IF [%SOLR_LOG_PRESTART_ROTATION%] == [true] (
+  call :run_utils "-remove_old_solr_logs 7" || echo "Failed removing old solr logs"
+  call :run_utils "-archive_gc_logs"        || echo "Failed archiving old GC logs"
+  call :run_utils "-archive_console_logs"   || echo "Failed archiving old console logs"
+  call :run_utils "-rotate_solr_logs 9"     || echo "Failed rotating old solr logs"
+)
 
 IF NOT "%ZK_HOST%"=="" set SOLR_MODE=solrcloud
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/075aec91/solr/bin/solr.in.cmd
----------------------------------------------------------------------
diff --git a/solr/bin/solr.in.cmd b/solr/bin/solr.in.cmd
index d323434..e565c02 100644
--- a/solr/bin/solr.in.cmd
+++ b/solr/bin/solr.in.cmd
@@ -75,6 +75,11 @@ REM set SOLR_LOG_LEVEL=INFO
 REM Location where Solr should write logs to. Absolute or relative to solr start dir
 REM set SOLR_LOGS_DIR=logs
 
+REM Enables log rotation, cleanup, and archiving before starting Solr. Setting SOLR_LOG_PRESTART_ROTATION=false will skip start
+REM time rotation of logs, and the archiving of the last GC and console log files. It does not affect Log4j configuration. This
+REM pre-startup rotation may need to be disabled depending how much you customize the default logging setup.
+REM set SOLR_LOG_PRESTART_ROTATION=true
+
 REM Set the host interface to listen on. Jetty will listen on all interfaces (0.0.0.0) by default.
 REM This must be an IPv4 ("a.b.c.d") or bracketed IPv6 ("[x::y]") address, not a hostname!
 REM set SOLR_JETTY_HOST=0.0.0.0

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/075aec91/solr/bin/solr.in.sh
----------------------------------------------------------------------
diff --git a/solr/bin/solr.in.sh b/solr/bin/solr.in.sh
index e5dd0c9..5a9f807 100644
--- a/solr/bin/solr.in.sh
+++ b/solr/bin/solr.in.sh
@@ -91,6 +91,11 @@
 # Location where Solr should write logs to. Absolute or relative to solr start dir
 #SOLR_LOGS_DIR=logs
 
+# Enables log rotation, cleanup, and archiving during start. Setting SOLR_LOG_PRESTART_ROTATION=false will skip start
+# time rotation of logs, and the archiving of the last GC and console log files. It does not affect Log4j configuration.
+# This pre-startup rotation may need to be disabled depending how much you customize the default logging setup.
+#SOLR_LOG_PRESTART_ROTATION=true
+
 # Sets the port Solr binds to, default is 8983
 #SOLR_PORT=8983
 


[12/38] lucene-solr:jira/solr-9857: LUCENE-7619: add WordDelimiterGraphFilter (replacing WordDelimiterFilter) to produce a correct token stream graph when splitting words

Posted by ab...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/test/org/apache/lucene/analysis/core/TestFlattenGraphFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/test/org/apache/lucene/analysis/core/TestFlattenGraphFilter.java b/lucene/analysis/common/src/test/org/apache/lucene/analysis/core/TestFlattenGraphFilter.java
new file mode 100644
index 0000000..c69bcca
--- /dev/null
+++ b/lucene/analysis/common/src/test/org/apache/lucene/analysis/core/TestFlattenGraphFilter.java
@@ -0,0 +1,284 @@
+/*
+ * 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.lucene.analysis.core;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.BaseTokenStreamTestCase;
+import org.apache.lucene.analysis.CannedTokenStream;
+import org.apache.lucene.analysis.MockTokenizer;
+import org.apache.lucene.analysis.Token;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.Tokenizer;
+
+public class TestFlattenGraphFilter extends BaseTokenStreamTestCase {
+  
+  private static Token token(String term, int posInc, int posLength, int startOffset, int endOffset) {
+    final Token t = new Token(term, startOffset, endOffset);
+    t.setPositionIncrement(posInc);
+    t.setPositionLength(posLength);
+    return t;
+  }
+
+  public void testSimpleMock() throws Exception {
+    Analyzer a = new Analyzer() {
+        @Override
+        protected TokenStreamComponents createComponents(String fieldName) {
+          Tokenizer tokenizer = new MockTokenizer(MockTokenizer.SIMPLE, true);
+          TokenStream ts = new FlattenGraphFilter(tokenizer);
+          return new TokenStreamComponents(tokenizer, ts);
+        }
+      };
+
+    assertAnalyzesTo(a, "wtf happened",
+                     new String[] {"wtf", "happened"},
+                     new int[]    {    0,          4},
+                     new int[]    {    3,         12},
+                     null,
+                     new int[]    {    1,          1},
+                     new int[]    {    1,          1},
+                     true);
+  }
+
+  // Make sure graph is unchanged if it's already flat
+  public void testAlreadyFlatten() throws Exception {
+    TokenStream in = new CannedTokenStream(0, 12, new Token[] {
+        token("wtf", 1, 1, 0, 3),
+        token("what", 0, 1, 0, 3),
+        token("wow", 0, 1, 0, 3),
+        token("the", 1, 1, 0, 3),
+        token("that's", 0, 1, 0, 3),
+        token("fudge", 1, 1, 0, 3),
+        token("funny", 0, 1, 0, 3),
+        token("happened", 1, 1, 4, 12)
+      });
+
+    TokenStream out = new FlattenGraphFilter(in);
+
+    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
+    assertTokenStreamContents(out,
+                              new String[] {"wtf", "what", "wow", "the", "that's", "fudge", "funny", "happened"},
+                              new int[] {0, 0, 0, 0, 0, 0, 0, 4},
+                              new int[] {3, 3, 3, 3, 3, 3, 3, 12},
+                              new int[] {1, 0, 0, 1, 0, 1, 0, 1},
+                              new int[] {1, 1, 1, 1, 1, 1, 1, 1},
+                              12);
+  }
+
+  public void testWTF1() throws Exception {
+
+    // "wow that's funny" and "what the fudge" are separate side paths, in parallel with "wtf", on input:
+    TokenStream in = new CannedTokenStream(0, 12, new Token[] {
+        token("wtf", 1, 5, 0, 3),
+        token("what", 0, 1, 0, 3),
+        token("wow", 0, 3, 0, 3),
+        token("the", 1, 1, 0, 3),
+        token("fudge", 1, 3, 0, 3),
+        token("that's", 1, 1, 0, 3),
+        token("funny", 1, 1, 0, 3),
+        token("happened", 1, 1, 4, 12)
+      });
+
+
+    TokenStream out = new FlattenGraphFilter(in);
+
+    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
+    assertTokenStreamContents(out,
+                              new String[] {"wtf", "what", "wow", "the", "that's", "fudge", "funny", "happened"},
+                              new int[] {0, 0, 0, 0, 0, 0, 0, 4},
+                              new int[] {3, 3, 3, 3, 3, 3, 3, 12},
+                              new int[] {1, 0, 0, 1, 0, 1, 0, 1},
+                              new int[] {3, 1, 1, 1, 1, 1, 1, 1},
+                              12);
+    
+  }
+
+  /** Same as testWTF1 except the "wtf" token comes out later */
+  public void testWTF2() throws Exception {
+
+    // "wow that's funny" and "what the fudge" are separate side paths, in parallel with "wtf", on input:
+    TokenStream in = new CannedTokenStream(0, 12, new Token[] {
+        token("what", 1, 1, 0, 3),
+        token("wow", 0, 3, 0, 3),
+        token("wtf", 0, 5, 0, 3),
+        token("the", 1, 1, 0, 3),
+        token("fudge", 1, 3, 0, 3),
+        token("that's", 1, 1, 0, 3),
+        token("funny", 1, 1, 0, 3),
+        token("happened", 1, 1, 4, 12)
+      });
+
+
+    TokenStream out = new FlattenGraphFilter(in);
+
+    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
+    assertTokenStreamContents(out,
+                              new String[] {"what", "wow", "wtf", "the", "that's", "fudge", "funny", "happened"},
+                              new int[] {0, 0, 0, 0, 0, 0, 0, 4},
+                              new int[] {3, 3, 3, 3, 3, 3, 3, 12},
+                              new int[] {1, 0, 0, 1, 0, 1, 0, 1},
+                              new int[] {1, 1, 3, 1, 1, 1, 1, 1},
+                              12);
+    
+  }
+
+  public void testNonGreedySynonyms() throws Exception {
+    // This is just "hypothetical" for Lucene today, because SynFilter is
+    // greedy: when two syn rules match on overlapping tokens, only one
+    // (greedily) wins.  This test pretends all syn matches could match:
+
+    TokenStream in = new CannedTokenStream(0, 20, new Token[] {
+        token("wizard", 1, 1, 0, 6),
+        token("wizard_of_oz", 0, 3, 0, 12),
+        token("of", 1, 1, 7, 9),
+        token("oz", 1, 1, 10, 12),
+        token("oz_screams", 0, 2, 10, 20),
+        token("screams", 1, 1, 13, 20),
+      });
+
+
+    TokenStream out = new FlattenGraphFilter(in);
+
+    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
+    assertTokenStreamContents(out,
+                              new String[] {"wizard", "wizard_of_oz", "of", "oz", "oz_screams", "screams"},
+                              new int[] {0, 0, 7, 10, 10, 13},
+                              new int[] {6, 12, 9, 12, 20, 20},
+                              new int[] {1, 0, 1, 1, 0, 1},
+                              new int[] {1, 3, 1, 1, 2, 1},
+                              20);
+    
+  }
+
+  public void testNonGraph() throws Exception {
+    TokenStream in = new CannedTokenStream(0, 22, new Token[] {
+        token("hello", 1, 1, 0, 5),
+        token("pseudo", 1, 1, 6, 12),
+        token("world", 1, 1, 13, 18),
+        token("fun", 1, 1, 19, 22),
+      });
+
+
+    TokenStream out = new FlattenGraphFilter(in);
+
+    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
+    assertTokenStreamContents(out,
+                              new String[] {"hello", "pseudo", "world", "fun"},
+                              new int[] {0, 6, 13, 19},
+                              new int[] {5, 12, 18, 22},
+                              new int[] {1, 1, 1, 1},
+                              new int[] {1, 1, 1, 1},
+                              22);
+  }
+
+  public void testSimpleHole() throws Exception {
+    TokenStream in = new CannedTokenStream(0, 13, new Token[] {
+        token("hello", 1, 1, 0, 5),
+        token("hole", 2, 1, 6, 10),
+        token("fun", 1, 1, 11, 13),
+      });
+
+
+    TokenStream out = new FlattenGraphFilter(in);
+
+    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
+    assertTokenStreamContents(out,
+                              new String[] {"hello", "hole", "fun"},
+                              new int[] {0, 6, 11},
+                              new int[] {5, 10, 13},
+                              new int[] {1, 2, 1},
+                              new int[] {1, 1, 1},
+                              13);
+  }
+
+  public void testHoleUnderSyn() throws Exception {
+    // Tests a StopFilter after SynFilter where a stopword in a syn is removed
+    //
+    //   wizard of oz -> woz syn, but then "of" becomes a hole
+
+    TokenStream in = new CannedTokenStream(0, 12, new Token[] {
+        token("wizard", 1, 1, 0, 6),
+        token("woz", 0, 3, 0, 12),
+        token("oz", 2, 1, 10, 12),
+      });
+
+
+    TokenStream out = new FlattenGraphFilter(in);
+
+    assertTokenStreamContents(out,
+                              new String[] {"wizard", "woz", "oz"},
+                              new int[] {0, 0, 10},
+                              new int[] {6, 12, 12},
+                              new int[] {1, 0, 2},
+                              new int[] {1, 3, 1},
+                              12);
+  }
+
+  public void testStrangelyNumberedNodes() throws Exception {
+
+    // Uses only nodes 0, 2, 3, i.e. 1 is just never used (it is not a hole!!)
+    TokenStream in = new CannedTokenStream(0, 27, new Token[] {
+        token("dog", 1, 3, 0, 5),
+        token("puppy", 0, 3, 0, 5),
+        token("flies", 3, 1, 6, 11),
+      });
+
+    TokenStream out = new FlattenGraphFilter(in);
+
+    assertTokenStreamContents(out,
+                              new String[] {"dog", "puppy", "flies"},
+                              new int[] {0, 0, 6},
+                              new int[] {5, 5, 11},
+                              new int[] {1, 0, 1},
+                              new int[] {1, 1, 1},
+                              27);
+  }
+
+  public void testTwoLongParallelPaths() throws Exception {
+
+    // "a a a a a a" in parallel with "b b b b b b"
+    TokenStream in = new CannedTokenStream(0, 11, new Token[] {
+        token("a", 1, 1, 0, 1),
+        token("b", 0, 2, 0, 1),
+        token("a", 1, 2, 2, 3),
+        token("b", 1, 2, 2, 3),
+        token("a", 1, 2, 4, 5),
+        token("b", 1, 2, 4, 5),
+        token("a", 1, 2, 6, 7),
+        token("b", 1, 2, 6, 7),
+        token("a", 1, 2, 8, 9),
+        token("b", 1, 2, 8, 9),
+        token("a", 1, 2, 10, 11),
+        token("b", 1, 2, 10, 11),
+      });
+
+
+    TokenStream out = new FlattenGraphFilter(in);
+    
+    // ... becomes flattened to a single path with overlapping a/b token between each node:
+    assertTokenStreamContents(out,
+                              new String[] {"a", "b", "a", "b", "a", "b", "a", "b", "a", "b", "a", "b"},
+                              new int[] {0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10},
+                              new int[] {1, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11, 11},
+                              new int[] {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0},
+                              new int[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+                              11);
+    
+  }
+
+  // NOTE: TestSynonymGraphFilter's testRandomSyns also tests FlattenGraphFilter
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterFilter.java b/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterFilter.java
index 7f35298..7f0481f 100644
--- a/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterFilter.java
+++ b/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterFilter.java
@@ -446,4 +446,73 @@ public class TestWordDelimiterFilter extends BaseTokenStreamTestCase {
       a.close();
     }
   }
+
+  /*
+  public void testToDot() throws Exception {
+    int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE | PRESERVE_ORIGINAL | CATENATE_WORDS | CATENATE_NUMBERS | STEM_ENGLISH_POSSESSIVE;
+    String text = "PowerSystem2000-5-Shot's";
+    WordDelimiterFilter wdf = new WordDelimiterFilter(new CannedTokenStream(new Token(text, 0, text.length())), DEFAULT_WORD_DELIM_TABLE, flags, null);
+    //StringWriter sw = new StringWriter();
+    // TokenStreamToDot toDot = new TokenStreamToDot(text, wdf, new PrintWriter(sw));
+    PrintWriter pw = new PrintWriter("/x/tmp/before.dot");
+    TokenStreamToDot toDot = new TokenStreamToDot(text, wdf, pw);
+    toDot.toDot();
+    pw.close();
+    System.out.println("TEST DONE");
+    //System.out.println("DOT:\n" + sw.toString());
+  }
+  */
+
+  public void testOnlyNumbers() throws Exception {
+    int flags = GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS;
+    Analyzer a = new Analyzer() {
+        
+      @Override
+      protected TokenStreamComponents createComponents(String fieldName) {
+        Tokenizer tokenizer = new MockTokenizer(MockTokenizer.WHITESPACE, false);
+        return new TokenStreamComponents(tokenizer, new WordDelimiterFilter(tokenizer, flags, null));
+      }
+    };
+
+    assertAnalyzesTo(a, "7-586", 
+                     new String[] {},
+                     new int[] {},
+                     new int[] {},
+                     null,
+                     new int[] {},
+                     null,
+                     false);
+  }
+
+  public void testNumberPunct() throws Exception {
+    int flags = GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS;
+    Analyzer a = new Analyzer() {
+        
+      @Override
+      protected TokenStreamComponents createComponents(String fieldName) {
+        Tokenizer tokenizer = new MockTokenizer(MockTokenizer.WHITESPACE, false);
+        return new TokenStreamComponents(tokenizer, new WordDelimiterFilter(tokenizer, flags, null));
+      }
+    };
+
+    assertAnalyzesTo(a, "6-", 
+                     new String[] {"6"},
+                     new int[] {0},
+                     new int[] {1},
+                     null,
+                     new int[] {1},
+                     null,
+                     false);
+  }
+
+  private Analyzer getAnalyzer(final int flags) {
+    return new Analyzer() {
+        
+      @Override
+      protected TokenStreamComponents createComponents(String fieldName) {
+        Tokenizer tokenizer = new MockTokenizer(MockTokenizer.WHITESPACE, false);
+        return new TokenStreamComponents(tokenizer, new WordDelimiterFilter(tokenizer, flags, null));
+      }
+    };
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterGraphFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterGraphFilter.java b/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterGraphFilter.java
new file mode 100644
index 0000000..2daf886
--- /dev/null
+++ b/lucene/analysis/common/src/test/org/apache/lucene/analysis/miscellaneous/TestWordDelimiterGraphFilter.java
@@ -0,0 +1,897 @@
+/*
+ * 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.lucene.analysis.miscellaneous;
+
+import java.io.IOException;
+import java.util.*;
+
+import org.apache.lucene.analysis.*;
+import org.apache.lucene.analysis.CharArraySet;
+import org.apache.lucene.analysis.StopFilter;
+import org.apache.lucene.analysis.core.KeywordTokenizer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
+import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.TestUtil;
+
+import static org.apache.lucene.analysis.miscellaneous.WordDelimiterGraphFilter.*;
+import static org.apache.lucene.analysis.miscellaneous.WordDelimiterIterator.DEFAULT_WORD_DELIM_TABLE;
+
+/**
+ * New WordDelimiterGraphFilter tests... most of the tests are in ConvertedLegacyTest
+ * TODO: should explicitly test things like protWords and not rely on
+ * the factory tests in Solr.
+ */
+public class TestWordDelimiterGraphFilter extends BaseTokenStreamTestCase {
+
+  public void testOffsets() throws IOException {
+    int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE;
+    // test that subwords and catenated subwords have
+    // the correct offsets.
+    WordDelimiterGraphFilter wdf = new WordDelimiterGraphFilter(new CannedTokenStream(new Token("foo-bar", 5, 12)), DEFAULT_WORD_DELIM_TABLE, flags, null);
+
+    assertTokenStreamContents(wdf, 
+                              new String[] { "foobar", "foo", "bar" },
+                              new int[] { 5, 5, 9 }, 
+                              new int[] { 12, 8, 12 });
+
+    // with illegal offsets:
+    wdf = new WordDelimiterGraphFilter(new CannedTokenStream(new Token("foo-bar", 5, 6)), DEFAULT_WORD_DELIM_TABLE, flags, null);
+    assertTokenStreamContents(wdf,
+                              new String[] { "foobar", "foo", "bar" },
+                              new int[] { 5, 5, 5 },
+                              new int[] { 6, 6, 6 });
+  }
+  
+  public void testOffsetChange() throws Exception {
+    int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE;
+    WordDelimiterGraphFilter wdf = new WordDelimiterGraphFilter(new CannedTokenStream(new Token("�belkeit)", 7, 16)), DEFAULT_WORD_DELIM_TABLE, flags, null);
+    
+    assertTokenStreamContents(wdf,
+        new String[] { "�belkeit" },
+        new int[] { 7 },
+        new int[] { 15 });
+  }
+  
+  public void testOffsetChange2() throws Exception {
+    int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE;
+    WordDelimiterGraphFilter wdf = new WordDelimiterGraphFilter(new CannedTokenStream(new Token("(�belkeit", 7, 17)), DEFAULT_WORD_DELIM_TABLE, flags, null);
+    // illegal offsets:
+    assertTokenStreamContents(wdf,
+                              new String[] { "�belkeit" },
+                              new int[] { 7 },
+                              new int[] { 17 });
+  }
+  
+  public void testOffsetChange3() throws Exception {
+    int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE;
+    WordDelimiterGraphFilter wdf = new WordDelimiterGraphFilter(new CannedTokenStream(new Token("(�belkeit", 7, 16)), DEFAULT_WORD_DELIM_TABLE, flags, null);
+    assertTokenStreamContents(wdf,
+                              new String[] { "�belkeit" },
+                              new int[] { 8 },
+                              new int[] { 16 });
+  }
+  
+  public void testOffsetChange4() throws Exception {
+    int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE;
+    WordDelimiterGraphFilter wdf = new WordDelimiterGraphFilter(new CannedTokenStream(new Token("(foo,bar)", 7, 16)), DEFAULT_WORD_DELIM_TABLE, flags, null);
+    
+    assertTokenStreamContents(wdf,
+        new String[] { "foobar", "foo", "bar"},
+        new int[] { 8, 8, 12 },
+        new int[] { 15, 11, 15 });
+  }
+
+  public void doSplit(final String input, String... output) throws Exception {
+    int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE;
+    WordDelimiterGraphFilter wdf = new WordDelimiterGraphFilter(keywordMockTokenizer(input),
+        WordDelimiterIterator.DEFAULT_WORD_DELIM_TABLE, flags, null);
+    
+    assertTokenStreamContents(wdf, output);
+  }
+
+  public void testSplits() throws Exception {
+    doSplit("basic-split","basic","split");
+    doSplit("camelCase","camel","Case");
+
+    // non-space marking symbol shouldn't cause split
+    // this is an example in Thai    
+    doSplit("\u0e1a\u0e49\u0e32\u0e19","\u0e1a\u0e49\u0e32\u0e19");
+    // possessive followed by delimiter
+    doSplit("test's'", "test");
+
+    // some russian upper and lowercase
+    doSplit("\u0420\u043e\u0431\u0435\u0440\u0442", "\u0420\u043e\u0431\u0435\u0440\u0442");
+    // now cause a split (russian camelCase)
+    doSplit("\u0420\u043e\u0431\u0415\u0440\u0442", "\u0420\u043e\u0431", "\u0415\u0440\u0442");
+
+    // a composed titlecase character, don't split
+    doSplit("a\u01c5ungla", "a\u01c5ungla");
+    
+    // a modifier letter, don't split
+    doSplit("\u0633\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0644\u0627\u0645", "\u0633\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0640\u0644\u0627\u0645");
+    
+    // enclosing mark, don't split
+    doSplit("test\u20dd", "test\u20dd");
+    
+    // combining spacing mark (the virama), don't split
+    doSplit("\u0939\u093f\u0928\u094d\u0926\u0940", "\u0939\u093f\u0928\u094d\u0926\u0940");
+    
+    // don't split non-ascii digits
+    doSplit("\u0661\u0662\u0663\u0664", "\u0661\u0662\u0663\u0664");
+    
+    // don't split supplementaries into unpaired surrogates
+    doSplit("\U00020000\U00020000", "\U00020000\U00020000");
+  }
+  
+  public void doSplitPossessive(int stemPossessive, final String input, final String... output) throws Exception {
+    int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS;
+    flags |= (stemPossessive == 1) ? STEM_ENGLISH_POSSESSIVE : 0;
+    WordDelimiterGraphFilter wdf = new WordDelimiterGraphFilter(keywordMockTokenizer(input), flags, null);
+
+    assertTokenStreamContents(wdf, output);
+  }
+  
+  /*
+   * Test option that allows disabling the special "'s" stemming, instead treating the single quote like other delimiters. 
+   */
+  public void testPossessives() throws Exception {
+    doSplitPossessive(1, "ra's", "ra");
+    doSplitPossessive(0, "ra's", "ra", "s");
+  }
+  
+  /*
+   * Set a large position increment gap of 10 if the token is "largegap" or "/"
+   */
+  private final class LargePosIncTokenFilter extends TokenFilter {
+    private CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
+    private PositionIncrementAttribute posIncAtt = addAttribute(PositionIncrementAttribute.class);
+    
+    protected LargePosIncTokenFilter(TokenStream input) {
+      super(input);
+    }
+
+    @Override
+    public boolean incrementToken() throws IOException {
+      if (input.incrementToken()) {
+        if (termAtt.toString().equals("largegap") || termAtt.toString().equals("/"))
+          posIncAtt.setPositionIncrement(10);
+        return true;
+      } else {
+        return false;
+      }
+    }  
+  }
+  
+  public void testPositionIncrements() throws Exception {
+    final int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE;
+    final CharArraySet protWords = new CharArraySet(new HashSet<>(Arrays.asList("NUTCH")), false);
+    
+    /* analyzer that uses whitespace + wdf */
+    Analyzer a = new Analyzer() {
+      @Override
+      public TokenStreamComponents createComponents(String field) {
+        Tokenizer tokenizer = new MockTokenizer(MockTokenizer.WHITESPACE, false);
+        return new TokenStreamComponents(tokenizer, new WordDelimiterGraphFilter(
+            tokenizer,
+            flags, protWords));
+      }
+    };
+
+    /* in this case, works as expected. */
+    assertAnalyzesTo(a, "LUCENE / SOLR", new String[] { "LUCENE", "SOLR" },
+        new int[] { 0, 9 },
+        new int[] { 6, 13 },
+        null,
+        new int[] { 1, 2 },
+        null,
+        false);
+
+    /* only in this case, posInc of 2 ?! */
+    assertAnalyzesTo(a, "LUCENE / solR", new String[] { "LUCENE", "solR", "sol", "R" },
+        new int[] { 0, 9, 9, 12 },
+        new int[] { 6, 13, 12, 13 },
+        null,                     
+        new int[] { 1, 2, 0, 1 },
+        null,
+        false);
+    
+    assertAnalyzesTo(a, "LUCENE / NUTCH SOLR", new String[] { "LUCENE", "NUTCH", "SOLR" },
+        new int[] { 0, 9, 15 },
+        new int[] { 6, 14, 19 },
+        null,
+        new int[] { 1, 2, 1 },
+        null,
+        false);
+    
+    /* analyzer that will consume tokens with large position increments */
+    Analyzer a2 = new Analyzer() {
+      @Override
+      public TokenStreamComponents createComponents(String field) {
+        Tokenizer tokenizer = new MockTokenizer(MockTokenizer.WHITESPACE, false);
+        return new TokenStreamComponents(tokenizer, new WordDelimiterGraphFilter(
+            new LargePosIncTokenFilter(tokenizer),
+            flags, protWords));
+      }
+    };
+    
+    /* increment of "largegap" is preserved */
+    assertAnalyzesTo(a2, "LUCENE largegap SOLR", new String[] { "LUCENE", "largegap", "SOLR" },
+        new int[] { 0, 7, 16 },
+        new int[] { 6, 15, 20 },
+        null,
+        new int[] { 1, 10, 1 },
+        null,
+        false);
+    
+    /* the "/" had a position increment of 10, where did it go?!?!! */
+    assertAnalyzesTo(a2, "LUCENE / SOLR", new String[] { "LUCENE", "SOLR" },
+        new int[] { 0, 9 },
+        new int[] { 6, 13 },
+        null,
+        new int[] { 1, 11 },
+        null,
+        false);
+    
+    /* in this case, the increment of 10 from the "/" is carried over */
+    assertAnalyzesTo(a2, "LUCENE / solR", new String[] { "LUCENE", "solR", "sol", "R" },
+        new int[] { 0, 9, 9, 12 },
+        new int[] { 6, 13, 12, 13 },
+        null,
+        new int[] { 1, 11, 0, 1 },
+        null,
+        false);
+    
+    assertAnalyzesTo(a2, "LUCENE / NUTCH SOLR", new String[] { "LUCENE", "NUTCH", "SOLR" },
+        new int[] { 0, 9, 15 },
+        new int[] { 6, 14, 19 },
+        null,
+        new int[] { 1, 11, 1 },
+        null,
+        false);
+
+    Analyzer a3 = new Analyzer() {
+      @Override
+      public TokenStreamComponents createComponents(String field) {
+        Tokenizer tokenizer = new MockTokenizer(MockTokenizer.WHITESPACE, false);
+        StopFilter filter = new StopFilter(tokenizer, StandardAnalyzer.STOP_WORDS_SET);
+        return new TokenStreamComponents(tokenizer, new WordDelimiterGraphFilter(filter, flags, protWords));
+      }
+    };
+
+    assertAnalyzesTo(a3, "lucene.solr", 
+        new String[] { "lucenesolr", "lucene", "solr" },
+        new int[] { 0, 0, 7 },
+        new int[] { 11, 6, 11 },
+        null,
+        new int[] { 1, 0, 1 },
+        null,
+        false);
+
+    /* the stopword should add a gap here */
+    assertAnalyzesTo(a3, "the lucene.solr", 
+        new String[] { "lucenesolr", "lucene", "solr" }, 
+        new int[] { 4, 4, 11 }, 
+        new int[] { 15, 10, 15 },
+        null,
+        new int[] { 2, 0, 1 },
+        null,
+        false);
+
+    IOUtils.close(a, a2, a3);
+  }
+  
+  /** concat numbers + words + all */
+  public void testLotsOfConcatenating() throws Exception {
+    final int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_WORDS | CATENATE_NUMBERS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE;    
+
+    /* analyzer that uses whitespace + wdf */
+    Analyzer a = new Analyzer() {
+      @Override
+      public TokenStreamComponents createComponents(String field) {
+        Tokenizer tokenizer = new MockTokenizer(MockTokenizer.WHITESPACE, false);
+        return new TokenStreamComponents(tokenizer, new WordDelimiterGraphFilter(tokenizer, flags, null));
+      }
+    };
+    
+    assertAnalyzesTo(a, "abc-def-123-456", 
+        new String[] { "abcdef123456", "abcdef", "abc", "def", "123456", "123", "456" }, 
+        new int[] { 0, 0, 0, 4, 8, 8, 12 }, 
+        new int[] { 15, 7, 3, 7, 15, 11, 15 },
+        null,
+        new int[] { 1, 0, 0, 1, 1, 0, 1 },
+        null,
+        false);
+    a.close();
+  }
+  
+  /** concat numbers + words + all + preserve original */
+  public void testLotsOfConcatenating2() throws Exception {
+    final int flags = PRESERVE_ORIGINAL | GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_WORDS | CATENATE_NUMBERS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE;    
+
+    /* analyzer that uses whitespace + wdf */
+    Analyzer a = new Analyzer() {
+      @Override
+      public TokenStreamComponents createComponents(String field) {
+        Tokenizer tokenizer = new MockTokenizer(MockTokenizer.WHITESPACE, false);
+        return new TokenStreamComponents(tokenizer, new WordDelimiterGraphFilter(tokenizer, flags, null));
+      }
+    };
+    
+    assertAnalyzesTo(a, "abc-def-123-456", 
+                     new String[] { "abcdef123456", "abc-def-123-456", "abcdef", "abc", "def", "123456", "123", "456" }, 
+                     new int[] { 0, 0, 0, 0, 4, 8, 8, 12 }, 
+                     new int[] { 15, 15, 7, 3, 7, 15, 11, 15 },
+                     null,
+                     new int[] { 1, 0, 0, 0, 1, 1, 0, 1 },
+                     null,
+                     false);
+    a.close();
+  }
+  
+  /** blast some random strings through the analyzer */
+  public void testRandomStrings() throws Exception {
+    int numIterations = atLeast(5);
+    for (int i = 0; i < numIterations; i++) {
+      final int flags = random().nextInt(512);
+      final CharArraySet protectedWords;
+      if (random().nextBoolean()) {
+        protectedWords = new CharArraySet(new HashSet<>(Arrays.asList("a", "b", "cd")), false);
+      } else {
+        protectedWords = null;
+      }
+      
+      Analyzer a = new Analyzer() {
+        
+        @Override
+        protected TokenStreamComponents createComponents(String fieldName) {
+          Tokenizer tokenizer = new MockTokenizer(MockTokenizer.WHITESPACE, false);
+          return new TokenStreamComponents(tokenizer, new WordDelimiterGraphFilter(tokenizer, flags, protectedWords));
+        }
+      };
+      // TODO: properly support positionLengthAttribute
+      checkRandomData(random(), a, 200*RANDOM_MULTIPLIER, 20, false, false);
+      a.close();
+    }
+  }
+  
+  /** blast some enormous random strings through the analyzer */
+  public void testRandomHugeStrings() throws Exception {
+    int numIterations = atLeast(5);
+    for (int i = 0; i < numIterations; i++) {
+      final int flags = random().nextInt(512);
+      final CharArraySet protectedWords;
+      if (random().nextBoolean()) {
+        protectedWords = new CharArraySet(new HashSet<>(Arrays.asList("a", "b", "cd")), false);
+      } else {
+        protectedWords = null;
+      }
+      
+      Analyzer a = new Analyzer() {
+        
+        @Override
+        protected TokenStreamComponents createComponents(String fieldName) {
+          Tokenizer tokenizer = new MockTokenizer(MockTokenizer.WHITESPACE, false);
+          TokenStream wdgf = new WordDelimiterGraphFilter(tokenizer, flags, protectedWords);
+          return new TokenStreamComponents(tokenizer, wdgf);
+        }
+      };
+      // TODO: properly support positionLengthAttribute
+      checkRandomData(random(), a, 20*RANDOM_MULTIPLIER, 8192, false, false);
+      a.close();
+    }
+  }
+  
+  public void testEmptyTerm() throws IOException {
+    Random random = random();
+    for (int i = 0; i < 512; i++) {
+      final int flags = i;
+      final CharArraySet protectedWords;
+      if (random.nextBoolean()) {
+        protectedWords = new CharArraySet(new HashSet<>(Arrays.asList("a", "b", "cd")), false);
+      } else {
+        protectedWords = null;
+      }
+    
+      Analyzer a = new Analyzer() { 
+        @Override
+        protected TokenStreamComponents createComponents(String fieldName) {
+          Tokenizer tokenizer = new KeywordTokenizer();
+          return new TokenStreamComponents(tokenizer, new WordDelimiterGraphFilter(tokenizer, flags, protectedWords));
+        }
+      };
+      // depending upon options, this thing may or may not preserve the empty term
+      checkAnalysisConsistency(random, a, random.nextBoolean(), "");
+      a.close();
+    }
+  }
+
+  private Analyzer getAnalyzer(int flags) {
+    return getAnalyzer(flags, null);
+  }
+  
+  private Analyzer getAnalyzer(int flags, CharArraySet protectedWords) {
+    return new Analyzer() { 
+      @Override
+      protected TokenStreamComponents createComponents(String fieldName) {
+        Tokenizer tokenizer = new KeywordTokenizer();
+        return new TokenStreamComponents(tokenizer, new WordDelimiterGraphFilter(tokenizer, flags, protectedWords));
+      }
+    };
+  }
+
+  private static boolean has(int flags, int flag) {
+    return (flags & flag) != 0;
+  }
+
+  private static boolean isEnglishPossessive(String text, int pos) {
+    if (pos > 2) {
+      if ((text.charAt(pos-1) == 's' || text.charAt(pos-1) == 'S') &&
+          (pos == text.length() || text.charAt(pos) != '-')) {
+        text = text.substring(0, text.length()-2);
+      }
+    }
+    return true;
+  }
+
+  private static class WordPart {
+    final String part;
+    final int startOffset;
+    final int endOffset;
+    final int type;
+
+    public WordPart(String text, int startOffset, int endOffset) {
+      this.part = text.substring(startOffset, endOffset);
+      this.startOffset = startOffset;
+      this.endOffset = endOffset;
+      this.type = toType(part.charAt(0));
+    }
+
+    @Override
+    public String toString() {
+      return "WordPart(" + part + " " + startOffset + "-" + endOffset + ")";
+    }
+  }
+
+  private static final int NUMBER = 0;
+  private static final int LETTER = 1;
+  private static final int DELIM = 2;
+
+  private static int toType(char ch) {
+    if (Character.isDigit(ch)) {
+      // numbers
+      return NUMBER;
+    } else if (Character.isLetter(ch)) {
+      // letters
+      return LETTER;
+    } else {
+      // delimiter
+      return DELIM;
+    }
+  }
+
+  /** Does (hopefully) the same thing as WordDelimiterGraphFilter, according to the flags, but more slowly, returning all string paths combinations. */
+  private Set<String> slowWDF(String text, int flags) {
+
+    // first make word parts:
+    List<WordPart> wordParts = new ArrayList<>();
+    int lastCH = -1;
+    int wordPartStart = 0;
+    boolean inToken = false;
+
+    for(int i=0;i<text.length();i++) {
+      char ch = text.charAt(i);
+      if (toType(ch) == DELIM) {
+        // delimiter
+        if (inToken) {
+          // end current token
+          wordParts.add(new WordPart(text, wordPartStart, i));
+          inToken = false;
+        }
+
+        // strip english possessive at the end of this token?:
+        if (has(flags, STEM_ENGLISH_POSSESSIVE) &&
+            ch == '\'' && i > 0 &&
+            i < text.length()-1 &&
+            (text.charAt(i+1) == 's' || text.charAt(i+1) == 'S') &&
+            toType(text.charAt(i-1)) == LETTER &&
+            (i+2 == text.length() || toType(text.charAt(i+2)) == DELIM)) {
+          i += 2;
+        }
+    
+      } else if (inToken == false) {
+        // start new token
+        inToken = true;
+        wordPartStart = i;
+      } else {
+        boolean newToken = false;
+        if (Character.isLetter(lastCH)) {
+          if (Character.isLetter(ch)) {
+            if (has(flags, SPLIT_ON_CASE_CHANGE) && Character.isLowerCase(lastCH) && Character.isLowerCase(ch) == false) {
+              // start new token on lower -> UPPER case change (but not vice versa!)
+              newToken = true;
+            }
+          } else if (has(flags, SPLIT_ON_NUMERICS) && Character.isDigit(ch)) {
+            // start new token on letter -> number change
+            newToken = true;
+          }
+        } else {
+          assert Character.isDigit(lastCH);
+          if (Character.isLetter(ch) && has(flags, SPLIT_ON_NUMERICS) ) {
+            // start new token on number -> letter change
+            newToken = true;
+          }
+        }
+        if (newToken) {
+          wordParts.add(new WordPart(text, wordPartStart, i));
+          wordPartStart = i;
+        }
+      }
+      lastCH = ch;
+    }
+
+    if (inToken) {
+      // add last token
+      wordParts.add(new WordPart(text, wordPartStart, text.length()));
+    }
+    
+    Set<String> paths = new HashSet<>();
+    if (wordParts.isEmpty() == false) {
+      enumerate(flags, 0, text, wordParts, paths, new StringBuilder());
+    }
+
+    if (has(flags, PRESERVE_ORIGINAL)) {
+      paths.add(text);
+    }
+
+    if (has(flags, CATENATE_ALL) && wordParts.isEmpty() == false) {
+      StringBuilder b = new StringBuilder();
+      for(WordPart wordPart : wordParts) {
+        b.append(wordPart.part);
+      }
+      paths.add(b.toString());
+    }
+    
+    return paths;
+  }
+
+  private void add(StringBuilder path, String part) {
+    if (path.length() != 0) {
+      path.append(' ');
+    }
+    path.append(part);
+  }
+
+  private void add(StringBuilder path, List<WordPart> wordParts, int from, int to) {
+    if (path.length() != 0) {
+      path.append(' ');
+    }
+    // no spaces:
+    for(int i=from;i<to;i++) {
+      path.append(wordParts.get(i).part);
+    }
+  }
+
+  private void addWithSpaces(StringBuilder path, List<WordPart> wordParts, int from, int to) {
+    for(int i=from;i<to;i++) {
+      add(path, wordParts.get(i).part);
+    }
+  }
+
+  /** Finds the end (exclusive) of the series of part with the same type */
+  private int endOfRun(List<WordPart> wordParts, int start) {
+    int upto = start+1;
+    while(upto < wordParts.size() && wordParts.get(upto).type == wordParts.get(start).type) {
+      upto++;
+    }
+    return upto;
+  }
+
+  /** Recursively enumerates all paths through the word parts */
+  private void enumerate(int flags, int upto, String text, List<WordPart> wordParts, Set<String> paths, StringBuilder path) {
+    if (upto == wordParts.size()) {
+      if (path.length() > 0) {
+        paths.add(path.toString());
+      }
+    } else {
+      int savLength = path.length();
+      int end = endOfRun(wordParts, upto);
+
+      if (wordParts.get(upto).type == NUMBER) {
+        // always output single word, optionally surrounded by delims:
+        if (has(flags, GENERATE_NUMBER_PARTS) || wordParts.size() == 1) {
+          addWithSpaces(path, wordParts, upto, end);
+          if (has(flags, CATENATE_NUMBERS)) {
+            // recurse first with the parts
+            enumerate(flags, end, text, wordParts, paths, path);
+            path.setLength(savLength);
+            // .. and second with the concat
+            add(path, wordParts, upto, end);
+          }
+        } else if (has(flags, CATENATE_NUMBERS)) {
+          add(path, wordParts, upto, end);
+        }
+        enumerate(flags, end, text, wordParts, paths, path);
+        path.setLength(savLength);
+      } else {
+        assert wordParts.get(upto).type == LETTER;
+        // always output single word, optionally surrounded by delims:
+        if (has(flags, GENERATE_WORD_PARTS) || wordParts.size() == 1) {
+          addWithSpaces(path, wordParts, upto, end);
+          if (has(flags, CATENATE_WORDS)) {
+            // recurse first with the parts
+            enumerate(flags, end, text, wordParts, paths, path);
+            path.setLength(savLength);
+            // .. and second with the concat
+            add(path, wordParts, upto, end);
+          }
+        } else if (has(flags, CATENATE_WORDS)) {
+          add(path, wordParts, upto, end);
+        }
+        enumerate(flags, end, text, wordParts, paths, path);
+        path.setLength(savLength);
+      }
+    }
+  }
+
+  public void testBasicGraphSplits() throws Exception {
+    assertGraphStrings(getAnalyzer(0),
+                       "PowerShotPlus",
+                       "PowerShotPlus");
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS),
+                       "PowerShotPlus",
+                       "PowerShotPlus");
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE),
+                       "PowerShotPlus",
+                       "Power Shot Plus");
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE | PRESERVE_ORIGINAL),
+                       "PowerShotPlus",
+                       "PowerShotPlus",
+                       "Power Shot Plus");
+
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS),
+                       "Power-Shot-Plus",
+                       "Power Shot Plus");
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE),
+                       "Power-Shot-Plus",
+                       "Power Shot Plus");
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE | PRESERVE_ORIGINAL),
+                       "Power-Shot-Plus",
+                       "Power-Shot-Plus",
+                       "Power Shot Plus");
+
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE),
+                       "PowerShotPlus",
+                       "Power Shot Plus");
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE),
+                       "PowerShot1000Plus",
+                       "Power Shot1000Plus");
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE),
+                       "Power-Shot-Plus",
+                       "Power Shot Plus");
+
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE | CATENATE_WORDS),
+                       "PowerShotPlus",
+                       "Power Shot Plus",
+                       "PowerShotPlus");
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE | CATENATE_WORDS),
+                       "PowerShot1000Plus",
+                       "Power Shot1000Plus",
+                       "PowerShot1000Plus");
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | SPLIT_ON_CASE_CHANGE | CATENATE_WORDS | CATENATE_NUMBERS),
+                       "Power-Shot-1000-17-Plus",
+                       "Power Shot 1000 17 Plus",
+                       "Power Shot 100017 Plus",
+                       "PowerShot 1000 17 Plus",
+                       "PowerShot 100017 Plus");
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | SPLIT_ON_CASE_CHANGE | CATENATE_WORDS | CATENATE_NUMBERS | PRESERVE_ORIGINAL),
+                       "Power-Shot-1000-17-Plus",
+                       "Power-Shot-1000-17-Plus",
+                       "Power Shot 1000 17 Plus",
+                       "Power Shot 100017 Plus",
+                       "PowerShot 1000 17 Plus",
+                       "PowerShot 100017 Plus");
+  }
+
+  /*
+  public void testToDot() throws Exception {
+    int flags = GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE | PRESERVE_ORIGINAL | CATENATE_WORDS | CATENATE_NUMBERS | STEM_ENGLISH_POSSESSIVE;
+    String text = "PowerSystem2000-5-Shot's";
+    WordDelimiterGraphFilter wdf = new WordDelimiterGraphFilter(new CannedTokenStream(new Token(text, 0, text.length())), DEFAULT_WORD_DELIM_TABLE, flags, null);
+    //StringWriter sw = new StringWriter();
+    // TokenStreamToDot toDot = new TokenStreamToDot(text, wdf, new PrintWriter(sw));
+    PrintWriter pw = new PrintWriter("/tmp/foo2.dot");
+    TokenStreamToDot toDot = new TokenStreamToDot(text, wdf, pw);
+    toDot.toDot();
+    pw.close();
+    //System.out.println("DOT:\n" + sw.toString());
+  }
+  */
+
+  private String randomWDFText() {
+    StringBuilder b = new StringBuilder();
+    int length = TestUtil.nextInt(random(), 1, 50);
+    for(int i=0;i<length;i++) {
+      int surpriseMe = random().nextInt(37);
+      int lower = -1;
+      int upper = -1;
+      if (surpriseMe < 10) {
+        // lowercase letter
+        lower = 'a';
+        upper = 'z';
+      } else if (surpriseMe < 20) {
+        // uppercase letter
+        lower = 'A';
+        upper = 'Z';
+      } else if (surpriseMe < 30) {
+        // digit
+        lower = '0';
+        upper = '9';
+      } else if (surpriseMe < 35) {
+        // punct
+        lower = '-';
+        upper = '-';
+      } else {
+        b.append("'s");
+      }
+
+      if (lower != -1) {
+        b.append((char) TestUtil.nextInt(random(), lower, upper));
+      }
+    }
+
+    return b.toString();
+  }
+
+  public void testInvalidFlag() throws Exception {
+    expectThrows(IllegalArgumentException.class,
+                 () -> {
+                   new WordDelimiterGraphFilter(new CannedTokenStream(), 1 << 31, null);
+                 });
+  }
+
+  public void testRandomPaths() throws Exception {
+    int iters = atLeast(100);
+    for(int iter=0;iter<iters;iter++) {
+      String text = randomWDFText();
+      if (VERBOSE) {
+        System.out.println("\nTEST: text=" + text + " len=" + text.length());
+      }
+
+      int flags = 0;
+      if (random().nextBoolean()) {
+        flags |= GENERATE_WORD_PARTS;
+      }
+      if (random().nextBoolean()) {
+        flags |= GENERATE_NUMBER_PARTS;
+      }
+      if (random().nextBoolean()) {
+        flags |= CATENATE_WORDS;
+      }
+      if (random().nextBoolean()) {
+        flags |= CATENATE_NUMBERS;
+      }
+      if (random().nextBoolean()) {
+        flags |= CATENATE_ALL;
+      }
+      if (random().nextBoolean()) {
+        flags |= PRESERVE_ORIGINAL;
+      }
+      if (random().nextBoolean()) {
+        flags |= SPLIT_ON_CASE_CHANGE;
+      }
+      if (random().nextBoolean()) {
+        flags |= SPLIT_ON_NUMERICS;
+      }
+      if (random().nextBoolean()) {
+        flags |= STEM_ENGLISH_POSSESSIVE;
+      }
+
+      verify(text, flags);
+    }
+  }
+
+  /** Runs normal and slow WDGF and compares results */
+  private void verify(String text, int flags) throws IOException {
+
+    Set<String> expected = slowWDF(text, flags);
+    if (VERBOSE) {
+      for(String path : expected) {
+        System.out.println("  " + path);
+      }
+    }
+
+    Set<String> actual = getGraphStrings(getAnalyzer(flags), text);
+    if (actual.equals(expected) == false) {
+      StringBuilder b = new StringBuilder();
+      b.append("\n\nFAIL: text=");
+      b.append(text);
+      b.append(" flags=");
+      b.append(WordDelimiterGraphFilter.flagsToString(flags));
+      b.append('\n');
+      b.append("  expected paths:\n");
+      for (String s : expected) {
+        b.append("    ");
+        b.append(s);
+        if (actual.contains(s) == false) {
+          b.append(" [missing!]");
+        }
+        b.append('\n');
+      }
+
+      b.append("  actual paths:\n");
+      for (String s : actual) {
+        b.append("    ");
+        b.append(s);
+        if (expected.contains(s) == false) {
+          b.append(" [unexpected!]");
+        }
+        b.append('\n');
+      }
+
+      fail(b.toString());
+    }
+  }
+
+  public void testOnlyNumbers() throws Exception {
+    // no token should be produced
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS), "7-586");
+  }
+
+  public void testNoCatenate() throws Exception {
+    // no token should be produced
+    assertGraphStrings(getAnalyzer(GENERATE_WORD_PARTS | GENERATE_NUMBER_PARTS | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS), "a-b-c-9-d", "a b c 9 d");
+  }
+
+  public void testCuriousCase1() throws Exception {
+    verify("u-0L-4836-ip4Gw--13--q7--L07E1", CATENATE_WORDS | CATENATE_ALL | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS | STEM_ENGLISH_POSSESSIVE);
+  }
+
+  public void testCuriousCase2() throws Exception {
+    verify("u-l-p", CATENATE_ALL);
+  }
+
+  public void testOriginalPosLength() throws Exception {
+    verify("Foo-Bar-Baz", CATENATE_WORDS | SPLIT_ON_CASE_CHANGE | PRESERVE_ORIGINAL);
+  }
+
+  public void testCuriousCase3() throws Exception {
+    verify("cQzk4-GL0izl0mKM-J8--4m-'s", GENERATE_NUMBER_PARTS | CATENATE_NUMBERS | SPLIT_ON_CASE_CHANGE | SPLIT_ON_NUMERICS);
+  }
+
+  public void testEmptyString() throws Exception {
+    WordDelimiterGraphFilter wdf = new WordDelimiterGraphFilter(new CannedTokenStream(new Token("", 0, 0)), DEFAULT_WORD_DELIM_TABLE, GENERATE_WORD_PARTS | CATENATE_ALL | PRESERVE_ORIGINAL, null);
+    wdf.reset();
+    assertTrue(wdf.incrementToken());
+    assertFalse(wdf.incrementToken());
+    wdf.end();
+    wdf.close();
+  }
+
+  public void testProtectedWords() throws Exception {
+    TokenStream tokens = new CannedTokenStream(new Token("foo17-bar", 0, 9),
+                                               new Token("foo-bar", 0, 7));
+
+    CharArraySet protectedWords = new CharArraySet(new HashSet<>(Arrays.asList("foo17-BAR")), true);
+    WordDelimiterGraphFilter wdf = new WordDelimiterGraphFilter(tokens, DEFAULT_WORD_DELIM_TABLE, GENERATE_WORD_PARTS | PRESERVE_ORIGINAL | CATENATE_ALL, protectedWords);
+    assertGraphStrings(wdf,
+                       "foo17-bar foo bar",
+                       "foo17-bar foo-bar",
+                       "foo17-bar foobar");
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/test/org/apache/lucene/analysis/synonym/TestFlattenGraphFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/test/org/apache/lucene/analysis/synonym/TestFlattenGraphFilter.java b/lucene/analysis/common/src/test/org/apache/lucene/analysis/synonym/TestFlattenGraphFilter.java
deleted file mode 100644
index d61fa96..0000000
--- a/lucene/analysis/common/src/test/org/apache/lucene/analysis/synonym/TestFlattenGraphFilter.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * 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.lucene.analysis.synonym;
-
-import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.analysis.BaseTokenStreamTestCase;
-import org.apache.lucene.analysis.CannedTokenStream;
-import org.apache.lucene.analysis.MockTokenizer;
-import org.apache.lucene.analysis.Token;
-import org.apache.lucene.analysis.TokenStream;
-import org.apache.lucene.analysis.Tokenizer;
-
-public class TestFlattenGraphFilter extends BaseTokenStreamTestCase {
-  
-  private static Token token(String term, int posInc, int posLength, int startOffset, int endOffset) {
-    final Token t = new Token(term, startOffset, endOffset);
-    t.setPositionIncrement(posInc);
-    t.setPositionLength(posLength);
-    return t;
-  }
-
-  public void testSimpleMock() throws Exception {
-    Analyzer a = new Analyzer() {
-        @Override
-        protected TokenStreamComponents createComponents(String fieldName) {
-          Tokenizer tokenizer = new MockTokenizer(MockTokenizer.SIMPLE, true);
-          TokenStream ts = new FlattenGraphFilter(tokenizer);
-          return new TokenStreamComponents(tokenizer, ts);
-        }
-      };
-
-    assertAnalyzesTo(a, "wtf happened",
-                     new String[] {"wtf", "happened"},
-                     new int[]    {    0,          4},
-                     new int[]    {    3,         12},
-                     null,
-                     new int[]    {    1,          1},
-                     new int[]    {    1,          1},
-                     true);
-  }
-
-  // Make sure graph is unchanged if it's already flat
-  public void testAlreadyFlatten() throws Exception {
-    TokenStream in = new CannedTokenStream(0, 12, new Token[] {
-        token("wtf", 1, 1, 0, 3),
-        token("what", 0, 1, 0, 3),
-        token("wow", 0, 1, 0, 3),
-        token("the", 1, 1, 0, 3),
-        token("that's", 0, 1, 0, 3),
-        token("fudge", 1, 1, 0, 3),
-        token("funny", 0, 1, 0, 3),
-        token("happened", 1, 1, 4, 12)
-      });
-
-    TokenStream out = new FlattenGraphFilter(in);
-
-    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
-    assertTokenStreamContents(out,
-                              new String[] {"wtf", "what", "wow", "the", "that's", "fudge", "funny", "happened"},
-                              new int[] {0, 0, 0, 0, 0, 0, 0, 4},
-                              new int[] {3, 3, 3, 3, 3, 3, 3, 12},
-                              new int[] {1, 0, 0, 1, 0, 1, 0, 1},
-                              new int[] {1, 1, 1, 1, 1, 1, 1, 1},
-                              12);
-  }
-
-  public void testWTF1() throws Exception {
-
-    // "wow that's funny" and "what the fudge" are separate side paths, in parallel with "wtf", on input:
-    TokenStream in = new CannedTokenStream(0, 12, new Token[] {
-        token("wtf", 1, 5, 0, 3),
-        token("what", 0, 1, 0, 3),
-        token("wow", 0, 3, 0, 3),
-        token("the", 1, 1, 0, 3),
-        token("fudge", 1, 3, 0, 3),
-        token("that's", 1, 1, 0, 3),
-        token("funny", 1, 1, 0, 3),
-        token("happened", 1, 1, 4, 12)
-      });
-
-
-    TokenStream out = new FlattenGraphFilter(in);
-
-    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
-    assertTokenStreamContents(out,
-                              new String[] {"wtf", "what", "wow", "the", "that's", "fudge", "funny", "happened"},
-                              new int[] {0, 0, 0, 0, 0, 0, 0, 4},
-                              new int[] {3, 3, 3, 3, 3, 3, 3, 12},
-                              new int[] {1, 0, 0, 1, 0, 1, 0, 1},
-                              new int[] {3, 1, 1, 1, 1, 1, 1, 1},
-                              12);
-    
-  }
-
-  /** Same as testWTF1 except the "wtf" token comes out later */
-  public void testWTF2() throws Exception {
-
-    // "wow that's funny" and "what the fudge" are separate side paths, in parallel with "wtf", on input:
-    TokenStream in = new CannedTokenStream(0, 12, new Token[] {
-        token("what", 1, 1, 0, 3),
-        token("wow", 0, 3, 0, 3),
-        token("wtf", 0, 5, 0, 3),
-        token("the", 1, 1, 0, 3),
-        token("fudge", 1, 3, 0, 3),
-        token("that's", 1, 1, 0, 3),
-        token("funny", 1, 1, 0, 3),
-        token("happened", 1, 1, 4, 12)
-      });
-
-
-    TokenStream out = new FlattenGraphFilter(in);
-
-    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
-    assertTokenStreamContents(out,
-                              new String[] {"what", "wow", "wtf", "the", "that's", "fudge", "funny", "happened"},
-                              new int[] {0, 0, 0, 0, 0, 0, 0, 4},
-                              new int[] {3, 3, 3, 3, 3, 3, 3, 12},
-                              new int[] {1, 0, 0, 1, 0, 1, 0, 1},
-                              new int[] {1, 1, 3, 1, 1, 1, 1, 1},
-                              12);
-    
-  }
-
-  public void testNonGreedySynonyms() throws Exception {
-    // This is just "hypothetical" for Lucene today, because SynFilter is
-    // greedy: when two syn rules match on overlapping tokens, only one
-    // (greedily) wins.  This test pretends all syn matches could match:
-
-    TokenStream in = new CannedTokenStream(0, 20, new Token[] {
-        token("wizard", 1, 1, 0, 6),
-        token("wizard_of_oz", 0, 3, 0, 12),
-        token("of", 1, 1, 7, 9),
-        token("oz", 1, 1, 10, 12),
-        token("oz_screams", 0, 2, 10, 20),
-        token("screams", 1, 1, 13, 20),
-      });
-
-
-    TokenStream out = new FlattenGraphFilter(in);
-
-    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
-    assertTokenStreamContents(out,
-                              new String[] {"wizard", "wizard_of_oz", "of", "oz", "oz_screams", "screams"},
-                              new int[] {0, 0, 7, 10, 10, 13},
-                              new int[] {6, 12, 9, 12, 20, 20},
-                              new int[] {1, 0, 1, 1, 0, 1},
-                              new int[] {1, 3, 1, 1, 2, 1},
-                              20);
-    
-  }
-
-  public void testNonGraph() throws Exception {
-    TokenStream in = new CannedTokenStream(0, 22, new Token[] {
-        token("hello", 1, 1, 0, 5),
-        token("pseudo", 1, 1, 6, 12),
-        token("world", 1, 1, 13, 18),
-        token("fun", 1, 1, 19, 22),
-      });
-
-
-    TokenStream out = new FlattenGraphFilter(in);
-
-    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
-    assertTokenStreamContents(out,
-                              new String[] {"hello", "pseudo", "world", "fun"},
-                              new int[] {0, 6, 13, 19},
-                              new int[] {5, 12, 18, 22},
-                              new int[] {1, 1, 1, 1},
-                              new int[] {1, 1, 1, 1},
-                              22);
-  }
-
-  public void testSimpleHole() throws Exception {
-    TokenStream in = new CannedTokenStream(0, 13, new Token[] {
-        token("hello", 1, 1, 0, 5),
-        token("hole", 2, 1, 6, 10),
-        token("fun", 1, 1, 11, 13),
-      });
-
-
-    TokenStream out = new FlattenGraphFilter(in);
-
-    // ... but on output, it's flattened to wtf/what/wow that's/the fudge/funny happened:
-    assertTokenStreamContents(out,
-                              new String[] {"hello", "hole", "fun"},
-                              new int[] {0, 6, 11},
-                              new int[] {5, 10, 13},
-                              new int[] {1, 2, 1},
-                              new int[] {1, 1, 1},
-                              13);
-  }
-
-  public void testHoleUnderSyn() throws Exception {
-    // Tests a StopFilter after SynFilter where a stopword in a syn is removed
-    //
-    //   wizard of oz -> woz syn, but then "of" becomes a hole
-
-    TokenStream in = new CannedTokenStream(0, 12, new Token[] {
-        token("wizard", 1, 1, 0, 6),
-        token("woz", 0, 3, 0, 12),
-        token("oz", 2, 1, 10, 12),
-      });
-
-
-    TokenStream out = new FlattenGraphFilter(in);
-
-    assertTokenStreamContents(out,
-                              new String[] {"wizard", "woz", "oz"},
-                              new int[] {0, 0, 10},
-                              new int[] {6, 12, 12},
-                              new int[] {1, 0, 2},
-                              new int[] {1, 3, 1},
-                              12);
-  }
-
-  public void testStrangelyNumberedNodes() throws Exception {
-
-    // Uses only nodes 0, 2, 3, i.e. 1 is just never used (it is not a hole!!)
-    TokenStream in = new CannedTokenStream(0, 27, new Token[] {
-        token("dog", 1, 3, 0, 5),
-        token("puppy", 0, 3, 0, 5),
-        token("flies", 3, 1, 6, 11),
-      });
-
-    TokenStream out = new FlattenGraphFilter(in);
-
-    assertTokenStreamContents(out,
-                              new String[] {"dog", "puppy", "flies"},
-                              new int[] {0, 0, 6},
-                              new int[] {5, 5, 11},
-                              new int[] {1, 0, 1},
-                              new int[] {1, 1, 1},
-                              27);
-  }
-
-  public void testTwoLongParallelPaths() throws Exception {
-
-    // "a a a a a a" in parallel with "b b b b b b"
-    TokenStream in = new CannedTokenStream(0, 11, new Token[] {
-        token("a", 1, 1, 0, 1),
-        token("b", 0, 2, 0, 1),
-        token("a", 1, 2, 2, 3),
-        token("b", 1, 2, 2, 3),
-        token("a", 1, 2, 4, 5),
-        token("b", 1, 2, 4, 5),
-        token("a", 1, 2, 6, 7),
-        token("b", 1, 2, 6, 7),
-        token("a", 1, 2, 8, 9),
-        token("b", 1, 2, 8, 9),
-        token("a", 1, 2, 10, 11),
-        token("b", 1, 2, 10, 11),
-      });
-
-
-    TokenStream out = new FlattenGraphFilter(in);
-    
-    // ... becomes flattened to a single path with overlapping a/b token between each node:
-    assertTokenStreamContents(out,
-                              new String[] {"a", "b", "a", "b", "a", "b", "a", "b", "a", "b", "a", "b"},
-                              new int[] {0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10},
-                              new int[] {1, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11, 11},
-                              new int[] {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0},
-                              new int[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-                              11);
-    
-  }
-
-  // NOTE: TestSynonymGraphFilter's testRandomSyns also tests FlattenGraphFilter
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/test/org/apache/lucene/analysis/synonym/TestSynonymGraphFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/test/org/apache/lucene/analysis/synonym/TestSynonymGraphFilter.java b/lucene/analysis/common/src/test/org/apache/lucene/analysis/synonym/TestSynonymGraphFilter.java
index edf2d2a..e00a165 100644
--- a/lucene/analysis/common/src/test/org/apache/lucene/analysis/synonym/TestSynonymGraphFilter.java
+++ b/lucene/analysis/common/src/test/org/apache/lucene/analysis/synonym/TestSynonymGraphFilter.java
@@ -17,14 +17,22 @@
 
 package org.apache.lucene.analysis.synonym;
 
+import java.io.IOException;
+import java.io.StringReader;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.BaseTokenStreamTestCase;
 import org.apache.lucene.analysis.MockAnalyzer;
 import org.apache.lucene.analysis.MockGraphTokenFilter;
 import org.apache.lucene.analysis.MockTokenizer;
 import org.apache.lucene.analysis.TokenStream;
-import org.apache.lucene.analysis.TokenStreamToAutomaton;
 import org.apache.lucene.analysis.Tokenizer;
+import org.apache.lucene.analysis.core.FlattenGraphFilter;
 import org.apache.lucene.analysis.tokenattributes.*;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
@@ -35,7 +43,6 @@ import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.store.ByteArrayDataInput;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.BytesRefBuilder;
 import org.apache.lucene.util.CharsRef;
 import org.apache.lucene.util.CharsRefBuilder;
 import org.apache.lucene.util.IOUtils;
@@ -49,15 +56,6 @@ import org.apache.lucene.util.automaton.TooComplexToDeterminizeException;
 import org.apache.lucene.util.automaton.Transition;
 import org.apache.lucene.util.fst.Util;
 
-import java.io.IOException;
-import java.io.StringReader;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
 public class TestSynonymGraphFilter extends BaseTokenStreamTestCase {
 
   /** Set as a side effect by {@link #getAnalyzer} and {@link #getFlattenAnalyzer}. */
@@ -1832,7 +1830,7 @@ public class TestSynonymGraphFilter extends BaseTokenStreamTestCase {
                      new int[]      {1,        1,   0,        0,     0,        1,   0,        0,   1,    0,         1,    1,         1},
                      new int[]      {1,        1,   1,        1,     4,        3,   1,        1,   2,    1,         1,    1,         1});
     
-    assertAllStrings(analyzer, "the usa is wealthy", new String[] {
+    assertGraphStrings(analyzer, "the usa is wealthy", new String[] {
         "the usa is wealthy",
         "the united states is wealthy",
         "the u s a is wealthy",
@@ -1924,33 +1922,4 @@ public class TestSynonymGraphFilter extends BaseTokenStreamTestCase {
         new int[]{1, 1, 0, 1, 1});
     a.close();
   }
-
-  /**
-   * Helper method to validate all strings that can be generated from a token stream.
-   * Uses {@link TokenStreamToAutomaton} to create an automaton. Asserts the finite strings of the automaton are all
-   * and only the given valid strings.
-   * @param analyzer analyzer containing the SynonymFilter under test.
-   * @param text text to be analyzed.
-   * @param expectedStrings all expected finite strings.
-   */
-  public void assertAllStrings(Analyzer analyzer, String text, String[] expectedStrings) throws IOException {
-    TokenStream tokenStream = analyzer.tokenStream("dummy", text);
-    try {
-      Automaton automaton = new TokenStreamToAutomaton().toAutomaton(tokenStream);
-      Set<IntsRef> finiteStrings = AutomatonTestUtil.getFiniteStringsRecursive(automaton, -1);
-
-      assertEquals("Invalid resulting strings count. Expected " + expectedStrings.length + " was " + finiteStrings.size(),
-          expectedStrings.length, finiteStrings.size());
-
-      Set<String> expectedStringsSet = new HashSet<>(Arrays.asList(expectedStrings));
-
-      BytesRefBuilder scratchBytesRefBuilder = new BytesRefBuilder();
-      for (IntsRef ir: finiteStrings) {
-        String s = Util.toBytesRef(ir, scratchBytesRefBuilder).utf8ToString().replace((char) TokenStreamToAutomaton.POS_SEP, ' ');
-        assertTrue("Unexpected string found: " + s, expectedStringsSet.contains(s));
-      }
-    } finally {
-      tokenStream.close();
-    }
-  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/core/src/java/org/apache/lucene/analysis/TokenStreamToAutomaton.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/analysis/TokenStreamToAutomaton.java b/lucene/core/src/java/org/apache/lucene/analysis/TokenStreamToAutomaton.java
index 64bac66..0675abe 100644
--- a/lucene/core/src/java/org/apache/lucene/analysis/TokenStreamToAutomaton.java
+++ b/lucene/core/src/java/org/apache/lucene/analysis/TokenStreamToAutomaton.java
@@ -39,6 +39,7 @@ import org.apache.lucene.util.automaton.Automaton;
 public class TokenStreamToAutomaton {
 
   private boolean preservePositionIncrements;
+  private boolean finalOffsetGapAsHole;
   private boolean unicodeArcs;
 
   /** Sole constructor. */
@@ -51,6 +52,11 @@ public class TokenStreamToAutomaton {
     this.preservePositionIncrements = enablePositionIncrements;
   }
 
+  /** If true, any final offset gaps will result in adding a position hole. */
+  public void setFinalOffsetGapAsHole(boolean finalOffsetGapAsHole) {
+    this.finalOffsetGapAsHole = finalOffsetGapAsHole;
+  }
+
   /** Whether to make transition labels Unicode code points instead of UTF8 bytes, 
    *  <code>false</code> by default */
   public void setUnicodeArcs(boolean unicodeArcs) {
@@ -118,7 +124,7 @@ public class TokenStreamToAutomaton {
     int maxOffset = 0;
     while (in.incrementToken()) {
       int posInc = posIncAtt.getPositionIncrement();
-      if (!preservePositionIncrements && posInc > 1) {
+      if (preservePositionIncrements == false && posInc > 1) {
         posInc = 1;
       }
       assert pos > -1 || posInc > 0;
@@ -201,10 +207,35 @@ public class TokenStreamToAutomaton {
     }
 
     in.end();
+
     int endState = -1;
-    if (offsetAtt.endOffset() > maxOffset) {
+
+    int endPosInc = posIncAtt.getPositionIncrement();
+
+    if (endPosInc == 0 && finalOffsetGapAsHole && offsetAtt.endOffset() > maxOffset) {
+      endPosInc = 1;
+    }
+    
+    if (endPosInc > 0) {
+      // there were hole(s) after the last token
       endState = builder.createState();
-      builder.setAccept(endState, true);
+
+      // add trailing holes now:
+      int lastState = endState;
+      while (true) {
+        int state1 = builder.createState();
+        builder.addTransition(lastState, state1, HOLE);
+        endPosInc--;
+        if (endPosInc == 0) {
+          builder.setAccept(state1, true);
+          break;
+        }
+        int state2 = builder.createState();
+        builder.addTransition(state1, state2, POS_SEP);
+        lastState = state2;
+      }
+    } else {
+      endState = -1;
     }
 
     pos++;
@@ -219,7 +250,7 @@ public class TokenStreamToAutomaton {
       }
       pos++;
     }
-
+    
     return builder.finish();
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/OffsetAttributeImpl.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/OffsetAttributeImpl.java b/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/OffsetAttributeImpl.java
index cdc5d42..166d6b2 100644
--- a/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/OffsetAttributeImpl.java
+++ b/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/OffsetAttributeImpl.java
@@ -43,7 +43,7 @@ public class OffsetAttributeImpl extends AttributeImpl implements OffsetAttribut
     // OffsetAtt
 
     if (startOffset < 0 || endOffset < startOffset) {
-      throw new IllegalArgumentException("startOffset must be non-negative, and endOffset must be >= startOffset, "
+      throw new IllegalArgumentException("startOffset must be non-negative, and endOffset must be >= startOffset; got "
           + "startOffset=" + startOffset + ",endOffset=" + endOffset);
     }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PackedTokenAttributeImpl.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PackedTokenAttributeImpl.java b/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PackedTokenAttributeImpl.java
index c89a374..ad1e232 100644
--- a/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PackedTokenAttributeImpl.java
+++ b/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PackedTokenAttributeImpl.java
@@ -107,7 +107,7 @@ public class PackedTokenAttributeImpl extends CharTermAttributeImpl
   @Override
   public void setOffset(int startOffset, int endOffset) {
     if (startOffset < 0 || endOffset < startOffset) {
-      throw new IllegalArgumentException("startOffset must be non-negative, and endOffset must be >= startOffset, "
+      throw new IllegalArgumentException("startOffset must be non-negative, and endOffset must be >= startOffset; got "
           + "startOffset=" + startOffset + ",endOffset=" + endOffset);
     }
     this.startOffset = startOffset;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionIncrementAttributeImpl.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionIncrementAttributeImpl.java b/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionIncrementAttributeImpl.java
index 4d63d6f..e89fec1 100644
--- a/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionIncrementAttributeImpl.java
+++ b/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionIncrementAttributeImpl.java
@@ -30,8 +30,7 @@ public class PositionIncrementAttributeImpl extends AttributeImpl implements Pos
   @Override
   public void setPositionIncrement(int positionIncrement) {
     if (positionIncrement < 0) {
-      throw new IllegalArgumentException
-        ("Increment must be zero or greater: got " + positionIncrement);
+      throw new IllegalArgumentException("Position increment must be zero or greater; got " + positionIncrement);
     }
     this.positionIncrement = positionIncrement;
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionLengthAttributeImpl.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionLengthAttributeImpl.java b/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionLengthAttributeImpl.java
index 9bfdb49..d019a2b 100644
--- a/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionLengthAttributeImpl.java
+++ b/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionLengthAttributeImpl.java
@@ -30,8 +30,7 @@ public class PositionLengthAttributeImpl extends AttributeImpl implements Positi
   @Override
   public void setPositionLength(int positionLength) {
     if (positionLength < 1) {
-      throw new IllegalArgumentException
-        ("Position length must be 1 or greater: got " + positionLength);
+      throw new IllegalArgumentException("Position length must be 1 or greater; got " + positionLength);
     }
     this.positionLength = positionLength;
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/core/src/test/org/apache/lucene/analysis/TestGraphTokenizers.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/analysis/TestGraphTokenizers.java b/lucene/core/src/test/org/apache/lucene/analysis/TestGraphTokenizers.java
index 8899dd1..7e98662 100644
--- a/lucene/core/src/test/org/apache/lucene/analysis/TestGraphTokenizers.java
+++ b/lucene/core/src/test/org/apache/lucene/analysis/TestGraphTokenizers.java
@@ -21,16 +21,22 @@ import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
+import java.util.Set;
 
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.lucene.util.IntsRef;
 import org.apache.lucene.util.automaton.Automata;
 import org.apache.lucene.util.automaton.Automaton;
+import org.apache.lucene.util.automaton.AutomatonTestUtil;
 import org.apache.lucene.util.automaton.Operations;
+import org.apache.lucene.util.fst.Util;
 
 import static org.apache.lucene.util.automaton.Operations.DEFAULT_MAX_DETERMINIZED_STATES;
 
@@ -565,7 +571,13 @@ public class TestGraphTokenizers extends BaseTokenStreamTestCase {
     assertSameLanguage(join(HOLE_A, SEP_A, s2a("abc")), ts);
   }
 
-  // TODO: testEndsWithHole... but we need posInc to set in TS.end()
+  public void testEndsWithHole() throws Exception {
+    final TokenStream ts = new CannedTokenStream(1, 0,
+                                                 new Token[] {
+                                                   token("abc", 2, 1),
+                                                 });
+    assertSameLanguage(join(HOLE_A, SEP_A, s2a("abc"), SEP_A, HOLE_A), ts);
+  }
 
   public void testSynHangingOverEnd() throws Exception {
     final TokenStream ts = new CannedTokenStream(
@@ -576,14 +588,47 @@ public class TestGraphTokenizers extends BaseTokenStreamTestCase {
     assertSameLanguage(Operations.union(s2a("a"), s2a("X")), ts);
   }
 
+  /** Returns all paths */
+  private Set<String> toPathStrings(Automaton a) {
+    BytesRefBuilder scratchBytesRefBuilder = new BytesRefBuilder();
+    Set<String> paths = new HashSet<>();
+    for (IntsRef ir: AutomatonTestUtil.getFiniteStringsRecursive(a, -1)) {
+      paths.add(Util.toBytesRef(ir, scratchBytesRefBuilder).utf8ToString().replace((char) TokenStreamToAutomaton.POS_SEP, ' '));
+    }
+    return paths;
+  }
+
   private void assertSameLanguage(Automaton expected, TokenStream ts) throws IOException {
     assertSameLanguage(expected, new TokenStreamToAutomaton().toAutomaton(ts));
   }
 
   private void assertSameLanguage(Automaton expected, Automaton actual) {
-    assertTrue(Operations.sameLanguage(
-      Operations.determinize(Operations.removeDeadStates(expected), DEFAULT_MAX_DETERMINIZED_STATES),
-      Operations.determinize(Operations.removeDeadStates(actual), DEFAULT_MAX_DETERMINIZED_STATES)));
+    Automaton expectedDet = Operations.determinize(Operations.removeDeadStates(expected), DEFAULT_MAX_DETERMINIZED_STATES);
+    Automaton actualDet = Operations.determinize(Operations.removeDeadStates(actual), DEFAULT_MAX_DETERMINIZED_STATES);
+    if (Operations.sameLanguage(expectedDet, actualDet) == false) {
+      Set<String> expectedPaths = toPathStrings(expectedDet);
+      Set<String> actualPaths = toPathStrings(actualDet);
+      StringBuilder b = new StringBuilder();
+      b.append("expected:\n");
+      for(String path : expectedPaths) {
+        b.append("  ");
+        b.append(path);
+        if (actualPaths.contains(path) == false) {
+          b.append(" [missing!]");
+        }
+        b.append('\n');
+      }
+      b.append("actual:\n");
+      for(String path : actualPaths) {
+        b.append("  ");
+        b.append(path);
+        if (expectedPaths.contains(path) == false) {
+          b.append(" [unexpected!]");
+        }
+        b.append('\n');
+      }
+      fail("accepted language is different:\n\n" + b.toString());
+    }
   }
 
   public void testTokenStreamGraphWithHoles() throws Exception {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingSuggester.java
----------------------------------------------------------------------
diff --git a/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingSuggester.java b/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingSuggester.java
index 19982a5..9c6a624 100644
--- a/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingSuggester.java
+++ b/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingSuggester.java
@@ -332,6 +332,7 @@ public class AnalyzingSuggester extends Lookup implements Accountable {
   TokenStreamToAutomaton getTokenStreamToAutomaton() {
     final TokenStreamToAutomaton tsta = new TokenStreamToAutomaton();
     tsta.setPreservePositionIncrements(preservePositionIncrements);
+    tsta.setFinalOffsetGapAsHole(true);
     return tsta;
   }
   
@@ -865,7 +866,7 @@ public class AnalyzingSuggester extends Lookup implements Accountable {
     // Turn tokenstream into automaton:
     Automaton automaton = null;
     try (TokenStream ts = queryAnalyzer.tokenStream("", key.toString())) {
-        automaton = getTokenStreamToAutomaton().toAutomaton(ts);
+      automaton = getTokenStreamToAutomaton().toAutomaton(ts);
     }
 
     automaton = replaceSep(automaton);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/test-framework/src/java/org/apache/lucene/analysis/BaseTokenStreamTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/analysis/BaseTokenStreamTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/analysis/BaseTokenStreamTestCase.java
index 924756e..070eab2 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/analysis/BaseTokenStreamTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/analysis/BaseTokenStreamTestCase.java
@@ -41,11 +41,16 @@ import org.apache.lucene.util.Attribute;
 import org.apache.lucene.util.AttributeFactory;
 import org.apache.lucene.util.AttributeImpl;
 import org.apache.lucene.util.AttributeReflector;
+import org.apache.lucene.util.BytesRefBuilder;
 import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.IntsRef;
 import org.apache.lucene.util.LineFileDocs;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.Rethrow;
 import org.apache.lucene.util.TestUtil;
+import org.apache.lucene.util.automaton.Automaton;
+import org.apache.lucene.util.automaton.AutomatonTestUtil;
+import org.apache.lucene.util.fst.Util;
 
 /** 
  * Base class for all Lucene unit tests that use TokenStreams. 
@@ -166,6 +171,8 @@ public abstract class BaseTokenStreamTestCase extends LuceneTestCase {
     final Map<Integer,Integer> posToStartOffset = new HashMap<>();
     final Map<Integer,Integer> posToEndOffset = new HashMap<>();
 
+    // TODO: would be nice to be able to assert silly duplicated tokens are not created, but a number of cases do this "legitimately": LUCENE-7622
+
     ts.reset();
     int pos = -1;
     int lastStartOffset = 0;
@@ -182,7 +189,7 @@ public abstract class BaseTokenStreamTestCase extends LuceneTestCase {
       checkClearAtt.getAndResetClearCalled(); // reset it, because we called clearAttribute() before
       assertTrue("token "+i+" does not exist", ts.incrementToken());
       assertTrue("clearAttributes() was not called correctly in TokenStream chain", checkClearAtt.getAndResetClearCalled());
-      
+
       assertEquals("term "+i, output[i], termAtt.toString());
       if (startOffsets != null) {
         assertEquals("startOffset " + i + " term=" + termAtt, startOffsets[i], offsetAtt.startOffset());
@@ -261,12 +268,12 @@ public abstract class BaseTokenStreamTestCase extends LuceneTestCase {
         }
       }
       if (posLengthAtt != null) {
-        assertTrue("posLength must be >= 1", posLengthAtt.getPositionLength() >= 1);
+        assertTrue("posLength must be >= 1; got: " + posLengthAtt.getPositionLength(), posLengthAtt.getPositionLength() >= 1);
       }
     }
 
     if (ts.incrementToken()) {
-      fail("TokenStream has more tokens than expected (expected count=" + output.length + "); extra token=" + termAtt);
+      fail("TokenStream has more tokens than expected (expected count=" + output.length + "); extra token=" + ts.getAttribute(CharTermAttribute.class));
     }
 
     // repeat our extra safety checks for end()
@@ -977,4 +984,105 @@ public abstract class BaseTokenStreamTestCase extends LuceneTestCase {
   public static AttributeFactory newAttributeFactory() {
     return newAttributeFactory(random());
   }
+
+  private static String toString(Set<String> strings) {
+    List<String> stringsList = new ArrayList<>(strings);
+    Collections.sort(stringsList);
+    StringBuilder b = new StringBuilder();
+    for(String s : stringsList) {
+      b.append("  ");
+      b.append(s);
+      b.append('\n');
+    }
+    return b.toString();
+  }
+
+  /**
+   * Enumerates all accepted strings in the token graph created by the analyzer on the provided text, and then
+   * asserts that it's equal to the expected strings.
+   * Uses {@link TokenStreamToAutomaton} to create an automaton. Asserts the finite strings of the automaton are all
+   * and only the given valid strings.
+   * @param analyzer analyzer containing the SynonymFilter under test.
+   * @param text text to be analyzed.
+   * @param expectedStrings all expected finite strings.
+   */
+  public static void assertGraphStrings(Analyzer analyzer, String text, String... expectedStrings) throws IOException {
+    checkAnalysisConsistency(random(), analyzer, true, text, true);
+    try (TokenStream tokenStream = analyzer.tokenStream("dummy", text)) {
+      assertGraphStrings(tokenStream, expectedStrings);
+    }
+  }
+
+  /**
+   * Enumerates all accepted strings in the token graph created by the already initialized {@link TokenStream}.
+   */
+  public static void assertGraphStrings(TokenStream tokenStream, String... expectedStrings) throws IOException {
+    Automaton automaton = new TokenStreamToAutomaton().toAutomaton(tokenStream);
+    Set<IntsRef> actualStringPaths = AutomatonTestUtil.getFiniteStringsRecursive(automaton, -1);
+
+    Set<String> expectedStringsSet = new HashSet<>(Arrays.asList(expectedStrings));
+
+    BytesRefBuilder scratchBytesRefBuilder = new BytesRefBuilder();
+    Set<String> actualStrings = new HashSet<>();
+    for (IntsRef ir: actualStringPaths) {
+      actualStrings.add(Util.toBytesRef(ir, scratchBytesRefBuilder).utf8ToString().replace((char) TokenStreamToAutomaton.POS_SEP, ' '));
+    }
+    for (String s : actualStrings) {
+      assertTrue("Analyzer created unexpected string path: " + s + "\nexpected:\n" + toString(expectedStringsSet) + "\nactual:\n" + toString(actualStrings), expectedStringsSet.contains(s));
+    }
+    for (String s : expectedStrings) {
+      assertTrue("Analyzer created unexpected string path: " + s + "\nexpected:\n" + toString(expectedStringsSet) + "\nactual:\n" + toString(actualStrings), actualStrings.contains(s));
+    }
+  }
+
+  /** Returns all paths accepted by the token stream graph produced by analyzing text with the provided analyzer.  The tokens {@link
+   *  CharTermAttribute} values are concatenated, and separated with space. */
+  public static Set<String> getGraphStrings(Analyzer analyzer, String text) throws IOException {
+    try(TokenStream tokenStream = analyzer.tokenStream("dummy", text)) {
+      return getGraphStrings(tokenStream);
+    }
+  }
+
+  /** Returns all paths accepted by the token stream graph produced by the already initialized {@link TokenStream}. */
+  public static Set<String> getGraphStrings(TokenStream tokenStream) throws IOException {
+    Automaton automaton = new TokenStreamToAutomaton().toAutomaton(tokenStream);
+    Set<IntsRef> actualStringPaths = AutomatonTestUtil.getFiniteStringsRecursive(automaton, -1);
+    BytesRefBuilder scratchBytesRefBuilder = new BytesRefBuilder();
+    Set<String> paths = new HashSet<>();
+    for (IntsRef ir: actualStringPaths) {
+      paths.add(Util.toBytesRef(ir, scratchBytesRefBuilder).utf8ToString().replace((char) TokenStreamToAutomaton.POS_SEP, ' '));
+    }
+    return paths;
+  }
+
+  /** Returns a {@code String} summary of the tokens this analyzer produces on this text */
+  public static String toString(Analyzer analyzer, String text) throws IOException {
+    try(TokenStream ts = analyzer.tokenStream("field", text)) {
+      StringBuilder b = new StringBuilder();
+      CharTermAttribute termAtt = ts.getAttribute(CharTermAttribute.class);
+      PositionIncrementAttribute posIncAtt = ts.getAttribute(PositionIncrementAttribute.class);
+      PositionLengthAttribute posLengthAtt = ts.getAttribute(PositionLengthAttribute.class);
+      OffsetAttribute offsetAtt = ts.getAttribute(OffsetAttribute.class);
+      assertNotNull(offsetAtt);
+      ts.reset();
+      int pos = -1;
+      while (ts.incrementToken()) {
+        pos += posIncAtt.getPositionIncrement();
+        b.append(termAtt);
+        b.append(" at pos=");
+        b.append(pos);
+        if (posLengthAtt != null) {
+          b.append(" to pos=");
+          b.append(pos + posLengthAtt.getPositionLength());
+        }
+        b.append(" offsets=");
+        b.append(offsetAtt.startOffset());
+        b.append('-');
+        b.append(offsetAtt.endOffset());
+        b.append('\n');
+      }
+      ts.end();
+      return b.toString();
+    }
+  }
 }


[18/38] lucene-solr:jira/solr-9857: LUCENE-7641: Speed up range queries that match most documents.

Posted by ab...@apache.org.
LUCENE-7641: Speed up range queries that match most documents.


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

Branch: refs/heads/jira/solr-9857
Commit: 3404677e57fcf7901813f7d7ccfc3e57db011993
Parents: 9ee48aa
Author: Adrien Grand <jp...@gmail.com>
Authored: Wed Jan 18 13:48:27 2017 +0100
Committer: Adrien Grand <jp...@gmail.com>
Committed: Wed Jan 18 13:48:27 2017 +0100

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |  6 ++
 .../org/apache/lucene/index/CheckIndex.java     |  4 +-
 .../apache/lucene/search/PointRangeQuery.java   | 74 ++++++++++++++++++++
 .../org/apache/lucene/util/bkd/BKDReader.java   |  8 ++-
 .../org/apache/lucene/util/bkd/BKDWriter.java   | 14 ++--
 .../apache/lucene/search/TestPointQueries.java  | 35 +++++++++
 6 files changed, 130 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/3404677e/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 4df7a67..cee0335 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -93,6 +93,12 @@ Improvements
   should be run, eg. using points or doc values depending on costs of other
   parts of the query. (Adrien Grand)
 
+Optimizations
+
+* LUCENE-7641: Optimized point range queries to compute documents that do not
+  match the range on single-valued fields when more than half the documents in
+  the index would match. (Adrien Grand)
+
 ======================= Lucene 6.4.0 =======================
 
 API Changes

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/3404677e/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java b/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
index 7611a7f..f3bdfb0 100644
--- a/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
+++ b/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
@@ -1813,8 +1813,8 @@ public final class CheckIndex implements Closeable {
             int docCount = values.getDocCount();
 
             final long crossCost = values.estimatePointCount(new ConstantRelationIntersectVisitor(Relation.CELL_CROSSES_QUERY));
-            if (crossCost < size) {
-              throw new RuntimeException("estimatePointCount should return >= size when all cells match");
+            if (crossCost < size / 2) {
+              throw new RuntimeException("estimatePointCount should return >= size/2 when all cells match");
             }
             final long insideCost = values.estimatePointCount(new ConstantRelationIntersectVisitor(Relation.CELL_INSIDE_QUERY));
             if (insideCost < size) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/3404677e/lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java b/lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java
index 29c6e7f..7c997ca 100644
--- a/lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java
@@ -26,7 +26,9 @@ import org.apache.lucene.index.PointValues.Relation;
 import org.apache.lucene.document.IntPoint;    // javadocs
 import org.apache.lucene.index.LeafReader;
 import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.util.BitSetIterator;
 import org.apache.lucene.util.DocIdSetBuilder;
+import org.apache.lucene.util.FixedBitSet;
 import org.apache.lucene.util.StringHelper;
 
 /** 
@@ -163,6 +165,64 @@ public abstract class PointRangeQuery extends Query {
         };
       }
 
+      /**
+       * Create a visitor that clears documents that do NOT match the range.
+       */
+      private IntersectVisitor getInverseIntersectVisitor(FixedBitSet result, int[] cost) {
+        return new IntersectVisitor() {
+
+          @Override
+          public void visit(int docID) {
+            result.clear(docID);
+            cost[0]--;
+          }
+
+          @Override
+          public void visit(int docID, byte[] packedValue) {
+            for(int dim=0;dim<numDims;dim++) {
+              int offset = dim*bytesPerDim;
+              if (StringHelper.compare(bytesPerDim, packedValue, offset, lowerPoint, offset) < 0) {
+                // Doc's value is too low, in this dimension
+                result.clear(docID);
+                cost[0]--;
+                return;
+              }
+              if (StringHelper.compare(bytesPerDim, packedValue, offset, upperPoint, offset) > 0) {
+                // Doc's value is too high, in this dimension
+                result.clear(docID);
+                cost[0]--;
+                return;
+              }
+            }
+          }
+
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+
+            boolean crosses = false;
+
+            for(int dim=0;dim<numDims;dim++) {
+              int offset = dim*bytesPerDim;
+
+              if (StringHelper.compare(bytesPerDim, minPackedValue, offset, upperPoint, offset) > 0 ||
+                  StringHelper.compare(bytesPerDim, maxPackedValue, offset, lowerPoint, offset) < 0) {
+                // This dim is not in the range
+                return Relation.CELL_INSIDE_QUERY;
+              }
+
+              crosses |= StringHelper.compare(bytesPerDim, minPackedValue, offset, lowerPoint, offset) < 0 ||
+                  StringHelper.compare(bytesPerDim, maxPackedValue, offset, upperPoint, offset) > 0;
+            }
+
+            if (crosses) {
+              return Relation.CELL_CROSSES_QUERY;
+            } else {
+              return Relation.CELL_OUTSIDE_QUERY;
+            }
+          }
+        };
+      }
+
       @Override
       public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
         LeafReader reader = context.reader();
@@ -221,6 +281,20 @@ public abstract class PointRangeQuery extends Query {
 
             @Override
             public Scorer get(boolean randomAccess) throws IOException {
+              if (values.getDocCount() == reader.maxDoc()
+                  && values.getDocCount() == values.size()
+                  && cost() > reader.maxDoc() / 2) {
+                // If all docs have exactly one value and the cost is greater
+                // than half the leaf size then maybe we can make things faster
+                // by computing the set of documents that do NOT match the range
+                final FixedBitSet result = new FixedBitSet(reader.maxDoc());
+                result.set(0, reader.maxDoc());
+                int[] cost = new int[] { reader.maxDoc() };
+                values.intersect(getInverseIntersectVisitor(result, cost));
+                final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]);
+                return new ConstantScoreScorer(weight, score(), iterator);
+              }
+
               values.intersect(visitor);
               DocIdSetIterator iterator = result.build().iterator();
               return new ConstantScoreScorer(weight, score(), iterator);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/3404677e/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
index 14e1adb..4089d82 100644
--- a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
+++ b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
@@ -717,8 +717,12 @@ public final class BKDReader extends PointValues implements Accountable {
       // This cell is fully outside of the query shape: stop recursing
       return 0L;
     } else if (state.index.isLeafNode()) {
-      // Assume all points match and there are no dups
-      return maxPointsInLeafNode;
+      if (r == Relation.CELL_INSIDE_QUERY) {
+        return maxPointsInLeafNode;
+      } else {
+        // Assume half the points matched
+        return (maxPointsInLeafNode + 1) / 2;
+      }
     } else {
       
       // Non-leaf node: recurse on the split left and right nodes

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/3404677e/lucene/core/src/java/org/apache/lucene/util/bkd/BKDWriter.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDWriter.java b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDWriter.java
index 5e391f4..eeb40fa 100644
--- a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDWriter.java
+++ b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDWriter.java
@@ -487,7 +487,7 @@ public class BKDWriter implements Closeable {
     assert Arrays.equals(parentSplits, new int[numDims]);
 
     long indexFP = out.getFilePointer();
-    writeIndex(out, leafBlockFPs, splitPackedValues);
+    writeIndex(out, Math.toIntExact(countPerLeaf), leafBlockFPs, splitPackedValues);
     return indexFP;
   }
 
@@ -645,7 +645,7 @@ public class BKDWriter implements Closeable {
       for(int i=0;i<leafBlockFPs.size();i++) {
         arr[i] = leafBlockFPs.get(i);
       }
-      writeIndex(out, arr, index);
+      writeIndex(out, maxPointsInLeafNode, arr, index);
       return indexFP;
     }
 
@@ -1035,7 +1035,7 @@ public class BKDWriter implements Closeable {
 
     // Write index:
     long indexFP = out.getFilePointer();
-    writeIndex(out, leafBlockFPs, splitPackedValues);
+    writeIndex(out, Math.toIntExact(countPerLeaf), leafBlockFPs, splitPackedValues);
     return indexFP;
   }
 
@@ -1241,16 +1241,16 @@ public class BKDWriter implements Closeable {
     return result;
   }
 
-  private void writeIndex(IndexOutput out, long[] leafBlockFPs, byte[] splitPackedValues) throws IOException {
+  private void writeIndex(IndexOutput out, int countPerLeaf, long[] leafBlockFPs, byte[] splitPackedValues) throws IOException {
     byte[] packedIndex = packIndex(leafBlockFPs, splitPackedValues);
-    writeIndex(out, leafBlockFPs.length, packedIndex);
+    writeIndex(out, countPerLeaf, leafBlockFPs.length, packedIndex);
   }
   
-  private void writeIndex(IndexOutput out, int numLeaves, byte[] packedIndex) throws IOException {
+  private void writeIndex(IndexOutput out, int countPerLeaf, int numLeaves, byte[] packedIndex) throws IOException {
     
     CodecUtil.writeHeader(out, CODEC_NAME, VERSION_CURRENT);
     out.writeVInt(numDims);
-    out.writeVInt(maxPointsInLeafNode);
+    out.writeVInt(countPerLeaf);
     out.writeVInt(bytesPerDim);
 
     assert numLeaves > 0;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/3404677e/lucene/core/src/test/org/apache/lucene/search/TestPointQueries.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestPointQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestPointQueries.java
index 5c66478..8f7beaf 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestPointQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestPointQueries.java
@@ -69,6 +69,7 @@ import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.NumericUtils;
 import org.apache.lucene.util.StringHelper;
 import org.apache.lucene.util.TestUtil;
+import org.apache.lucene.util.bkd.BKDWriter;
 import org.junit.BeforeClass;
 
 public class TestPointQueries extends LuceneTestCase {
@@ -2080,4 +2081,38 @@ public class TestPointQueries extends LuceneTestCase {
     assertTrue(Float.compare(Float.NEGATIVE_INFINITY, FloatPoint.nextDown(Float.NEGATIVE_INFINITY)) == 0);
     assertTrue(Float.compare(Float.MAX_VALUE, FloatPoint.nextDown(Float.POSITIVE_INFINITY)) == 0);
   }
+
+  public void testInversePointRange() throws IOException {
+    Directory dir = newDirectory();
+    IndexWriter w = new IndexWriter(dir, newIndexWriterConfig());
+    final int numDims = TestUtil.nextInt(random(), 1, 3);
+    final int numDocs = atLeast(10 * BKDWriter.DEFAULT_MAX_POINTS_IN_LEAF_NODE); // we need multiple leaves to enable this optimization
+    for (int i = 0; i < numDocs; ++i) {
+      Document doc = new Document();
+      int[] values = new int[numDims];
+      Arrays.fill(values, i);
+      doc.add(new IntPoint("f", values));
+      w.addDocument(doc);
+    }
+    w.forceMerge(1);
+    IndexReader r = DirectoryReader.open(w);
+    w.close();
+
+    IndexSearcher searcher = newSearcher(r);
+    int[] low = new int[numDims];
+    int[] high = new int[numDims];
+    Arrays.fill(high, numDocs - 2);
+    assertEquals(high[0] - low[0] + 1, searcher.count(IntPoint.newRangeQuery("f", low, high)));
+    Arrays.fill(low, 1);
+    assertEquals(high[0] - low[0] + 1, searcher.count(IntPoint.newRangeQuery("f", low, high)));
+    Arrays.fill(high, numDocs - 1);
+    assertEquals(high[0] - low[0] + 1, searcher.count(IntPoint.newRangeQuery("f", low, high)));
+    Arrays.fill(low, BKDWriter.DEFAULT_MAX_POINTS_IN_LEAF_NODE + 1);
+    assertEquals(high[0] - low[0] + 1, searcher.count(IntPoint.newRangeQuery("f", low, high)));
+    Arrays.fill(high, numDocs - BKDWriter.DEFAULT_MAX_POINTS_IN_LEAF_NODE);
+    assertEquals(high[0] - low[0] + 1, searcher.count(IntPoint.newRangeQuery("f", low, high)));
+
+    r.close();
+    dir.close();
+  }
 }


[02/38] lucene-solr:jira/solr-9857: Remove unnecessary @Override annotation in CoreParser.java class.

Posted by ab...@apache.org.
Remove unnecessary @Override annotation in CoreParser.java class.


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

Branch: refs/heads/jira/solr-9857
Commit: 649c58de0252ba608963e0fe699f8332c870b294
Parents: efc7ee0
Author: Christine Poerschke <cp...@apache.org>
Authored: Mon Jan 16 15:10:51 2017 +0000
Committer: Christine Poerschke <cp...@apache.org>
Committed: Mon Jan 16 15:29:30 2017 +0000

----------------------------------------------------------------------
 .../src/java/org/apache/lucene/queryparser/xml/CoreParser.java     | 2 --
 1 file changed, 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/649c58de/lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/CoreParser.java
----------------------------------------------------------------------
diff --git a/lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/CoreParser.java b/lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/CoreParser.java
index 1bf82ac..d8aa8ef 100644
--- a/lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/CoreParser.java
+++ b/lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/CoreParser.java
@@ -141,8 +141,6 @@ public class CoreParser implements QueryBuilder {
     return doc;
   }
 
-
-  @Override
   public Query getQuery(Element e) throws ParserException {
     return queryFactory.getQuery(e);
   }


[28/38] lucene-solr:jira/solr-9857: SOLR-8396: Add support for PointFields in Solr

Posted by ab...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/schema/TestPointFields.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/TestPointFields.java b/solr/core/src/test/org/apache/solr/schema/TestPointFields.java
new file mode 100644
index 0000000..12f1504
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/schema/TestPointFields.java
@@ -0,0 +1,1472 @@
+/*
+ * 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.schema;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.schema.DoublePointField;
+import org.apache.solr.schema.IntPointField;
+import org.apache.solr.schema.PointField;
+import org.apache.solr.schema.SchemaField;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Tests for PointField functionality
+ *
+ *
+ */
+public class TestPointFields extends SolrTestCaseJ4 {
+  
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    initCore("solrconfig.xml","schema-point.xml");
+  }
+  
+  @Override
+  @After
+  public void tearDown() throws Exception {
+    clearIndex();
+    assertU(commit());
+    super.tearDown();
+  }
+  
+  @Test
+  public void testIntPointFieldExactQuery() throws Exception {
+    doTestIntPointFieldExactQuery("number_p_i", false);
+    doTestIntPointFieldExactQuery("number_p_i_mv", false);
+    doTestIntPointFieldExactQuery("number_p_i_ni_dv", false);
+    // uncomment once MultiValued docValues are supported in PointFields
+    //    doTestIntPointFieldExactQuery("number_p_i_ni_mv_dv", false);
+  }
+  
+  @Test
+  public void testIntPointFieldReturn() throws Exception {
+    testPointFieldReturn("number_p_i", "int", new String[]{"0", "-1", "2", "3", "43", "52", "-60", "74", "80", "99"});
+    clearIndex();
+    assertU(commit());
+    testPointFieldReturn("number_p_i_dv_ns", "int", new String[]{"0", "-1", "2", "3", "43", "52", "-60", "74", "80", "99"});
+  }
+  
+  @Test
+  public void testIntPointFieldRangeQuery() throws Exception {
+    doTestIntPointFieldRangeQuery("number_p_i", "int", false);
+  }
+  
+  @Test
+  public void testIntPointFieldSort() throws Exception {
+    doTestPointFieldSort("number_p_i", "number_p_i_dv", new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"});
+  }
+  
+  @Test
+  public void testIntPointFieldFacetField() throws Exception {
+    testPointFieldFacetField("number_p_i", "number_p_i_dv", getSequentialStringArrayWithInts(10));
+  }
+
+  @Test
+  public void testIntPointFieldRangeFacet() throws Exception {
+    doTestIntPointFieldRangeFacet("number_p_i_dv", "number_p_i");
+  }
+  
+  
+  @Test
+  public void testIntPointFunctionQuery() throws Exception {
+    doTestIntPointFunctionQuery("number_p_i_dv", "number_p_i", "int");
+  }
+
+
+  @Test
+  public void testIntPointStats() throws Exception {
+    testPointStats("number_p_i", "number_p_i_dv", new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"},
+        0D, 9D, "10", "1", 0D);
+  }
+
+  @Test
+  public void testIntPointFieldMultiValuedExactQuery() throws Exception {
+    testPointFieldMultiValuedExactQuery("number_p_i_mv", getSequentialStringArrayWithInts(20));
+  }
+  
+  @Test
+  public void testIntPointFieldMultiValuedReturn() throws Exception {
+    testPointFieldMultiValuedReturn("number_p_i_mv", "int", getSequentialStringArrayWithInts(20));
+  }
+  
+  @Test
+  public void testIntPointFieldMultiValuedRangeQuery() throws Exception {
+    testPointFieldMultiValuedRangeQuery("number_p_i_mv", "int", getSequentialStringArrayWithInts(20));
+  }
+  
+  //TODO MV SORT?
+  @Test
+  @Ignore("Enable once MultiValued docValues are supported in PointFields")
+  public void testIntPointFieldMultiValuedFacetField() throws Exception {
+    testPointFieldMultiValuedFacetField("number_p_i_mv", "number_p_i_mv_dv", getSequentialStringArrayWithInts(20));
+  }
+
+  @Test
+  @Ignore("Enable once MultiValued docValues are supported in PointFields")
+  public void testIntPointFieldMultiValuedRangeFacet() throws Exception {
+    String docValuesField = "number_p_i_mv_dv";
+    String nonDocValuesField = "number_p_i_mv";
+    
+    for (int i = 0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), docValuesField, String.valueOf(i), docValuesField, String.valueOf(i + 10), 
+          nonDocValuesField, String.valueOf(i), nonDocValuesField, String.valueOf(i + 10)));
+    }
+    assertU(commit());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).getType() instanceof IntPointField);
+    assertQ(req("q", "*:*", "fl", "id", "facet", "true", "facet.range", docValuesField, "facet.range.start", "-10", "facet.range.end", "20", "facet.range.gap", "2"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='2'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='4'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='6'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='8'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='10'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='12'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='14'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='16'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='18'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='-10'][.='0']");
+    
+    assertQ(req("q", "*:*", "fl", "id", "facet", "true", "facet.range", docValuesField, "facet.range.start", "-10", "facet.range.end", "20", "facet.range.gap", "2", "facet.range.method", "dv"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='2'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='4'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='6'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='8'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='10'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='12'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='14'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='16'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='18'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='-10'][.='0']");
+    
+    assertQ(req("q", "*:*", "fl", "id", "facet", "true", "facet.range", docValuesField, "facet.range.start", "0", "facet.range.end", "20", "facet.range.gap", "100"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='0'][.='10']");
+    
+    assertFalse(h.getCore().getLatestSchema().getField(nonDocValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(nonDocValuesField).getType() instanceof IntPointField);
+    // Range Faceting with method = filter should work
+    assertQ(req("q", "*:*", "fl", "id", "facet", "true", "facet.range", nonDocValuesField, "facet.range.start", "-10", "facet.range.end", "20", "facet.range.gap", "2", "facet.range.method", "filter"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='2'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='4'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='6'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='8'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='10'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='12'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='14'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='16'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='18'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='-10'][.='0']");
+    
+    // this should actually use filter method instead of dv
+    assertQ(req("q", "*:*", "fl", "id", "facet", "true", "facet.range", nonDocValuesField, "facet.range.start", "-10", "facet.range.end", "20", "facet.range.gap", "2", "facet.range.method", "dv"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='2'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='4'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='6'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='8'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='10'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='12'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='14'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='16'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='18'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='-10'][.='0']");
+  }
+
+  @Test
+  @Ignore("Enable once MultiValued docValues are supported in PointFields")
+  public void testIntPointMultiValuedFunctionQuery() throws Exception {
+    testPointMultiValuedFunctionQuery("number_p_i_mv", "number_p_i_mv_dv", "int", getSequentialStringArrayWithInts(20));
+  }
+  
+  @Test
+  public void testIntPointFieldsAtomicUpdates() throws Exception {
+    if (!Boolean.getBoolean("enable.update.log")) {
+      return;
+    }
+    testIntPointFieldsAtomicUpdates("number_p_i", "int");
+    testIntPointFieldsAtomicUpdates("number_p_i_dv", "int");
+    testIntPointFieldsAtomicUpdates("number_p_i_dv_ns", "int");
+  }
+  
+  @Test
+  public void testIntPointSetQuery() throws Exception {
+    doTestSetQueries("number_p_i", getRandomStringArrayWithInts(10, false), false);
+    doTestSetQueries("number_p_i_mv", getRandomStringArrayWithInts(10, false), true);
+    doTestSetQueries("number_p_i_ni_dv", getRandomStringArrayWithInts(10, false), false);
+  }
+  
+  // DoublePointField
+
+  @Test
+  public void testDoublePointFieldExactQuery() throws Exception {
+    doTestFloatPointFieldExactQuery("number_d");
+    doTestFloatPointFieldExactQuery("number_p_d");
+    doTestFloatPointFieldExactQuery("number_p_d_mv");
+    doTestFloatPointFieldExactQuery("number_p_d_ni_dv");
+    // TODO enable once MuultiValued docValues are supported with PointFields
+//    doTestFloatPointFieldExactQuery("number_p_d_ni_mv_dv");
+  }
+  
+  @Test
+  public void testDoublePointFieldReturn() throws Exception {
+    testPointFieldReturn("number_p_d", "double", new String[]{"0.0", "1.2", "2.5", "3.02", "0.43", "5.2", "6.01", "74.0", "80.0", "9.9"});
+    clearIndex();
+    assertU(commit());
+    testPointFieldReturn("number_p_d_dv_ns", "double", new String[]{"0.0", "1.2", "2.5", "3.02", "0.43", "5.2", "6.01", "74.0", "80.0", "9.9"});
+    clearIndex();
+    assertU(commit());
+    String[] arr = new String[atLeast(10)];
+    for (int i = 0; i < arr.length; i++) {
+      double rand = random().nextDouble() * 10;
+      arr[i] = String.valueOf(rand);
+    }
+    testPointFieldReturn("number_p_d", "double", arr);
+  }
+  
+  @Test
+  public void testDoublePointFieldRangeQuery() throws Exception {
+    doTestFloatPointFieldRangeQuery("number_p_d", "double", true);
+  }
+  
+  @Test
+  public void testDoublePointFieldSort() throws Exception {
+    String[] arr = getRandomStringArrayWithDoubles(10, true);
+    doTestPointFieldSort("number_p_d", "number_p_d_dv", arr);
+  }
+  
+  @Test
+  public void testDoublePointFieldFacetField() throws Exception {
+    testPointFieldFacetField("number_p_d", "number_p_d_dv", getSequentialStringArrayWithDoubles(10));
+    clearIndex();
+    assertU(commit());
+    testPointFieldFacetField("number_p_d", "number_p_d_dv", getRandomStringArrayWithDoubles(10, false));
+  }
+
+  @Test
+  public void testDoublePointFieldRangeFacet() throws Exception {
+    doTestFloatPointFieldRangeFacet("number_p_d_dv", "number_p_d");
+  }
+
+  @Test
+  public void testDoublePointFunctionQuery() throws Exception {
+    doTestFloatPointFunctionQuery("number_p_d_dv", "number_p_d", "double");
+  }
+  
+  @Test
+  public void testDoublePointStats() throws Exception {
+    testPointStats("number_p_d", "number_p_d_dv", new String[]{"-10.0", "1.1", "2.2", "3.3", "4.4", "5.5", "6.6", "7.7", "8.8", "9.9"},
+        -10.0D, 9.9D, "10", "1", 1E-10D);
+  }
+  
+  @Test
+  public void testDoublePointFieldMultiValuedExactQuery() throws Exception {
+    testPointFieldMultiValuedExactQuery("number_p_d_mv", getRandomStringArrayWithDoubles(20, false));
+  }
+  
+  @Test
+  public void testDoublePointFieldMultiValuedReturn() throws Exception {
+    testPointFieldMultiValuedReturn("number_p_d_mv", "double", getSequentialStringArrayWithDoubles(20));
+  }
+  
+  @Test
+  public void testDoublePointFieldMultiValuedRangeQuery() throws Exception {
+    testPointFieldMultiValuedRangeQuery("number_p_d_mv", "double", getSequentialStringArrayWithDoubles(20));
+  }
+  
+  @Test
+  @Ignore("Enable once MultiValued docValues are supported in PointFields")
+  public void testDoublePointFieldMultiValuedFacetField() throws Exception {
+    testPointFieldMultiValuedFacetField("number_p_d_mv", "number_p_d_mv_dv", getSequentialStringArrayWithDoubles(20));
+    testPointFieldMultiValuedFacetField("number_p_d_mv", "number_p_d_mv_dv", getRandomStringArrayWithDoubles(20, false));
+  }
+
+  @Test
+  @Ignore("Enable once MultiValued docValues are supported in PointFields")
+  public void testDoublePointFieldMultiValuedRangeFacet() throws Exception {
+    String docValuesField = "number_p_d_mv_dv";
+    String nonDocValuesField = "number_p_d_mv";
+    
+    for (int i = 0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), docValuesField, String.valueOf(i), docValuesField, String.valueOf(i + 10), 
+          nonDocValuesField, String.valueOf(i), nonDocValuesField, String.valueOf(i + 10)));
+    }
+    assertU(commit());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).multiValued());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).getType() instanceof DoublePointField);
+    assertQ(req("q", "*:*", "fl", "id", "facet", "true", "facet.range", docValuesField, "facet.range.start", "-10", "facet.range.end", "20", "facet.range.gap", "2"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='0.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='2.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='4.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='6.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='8.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='10.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='12.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='14.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='16.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='18.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='-10.0'][.='0']");
+    
+    assertQ(req("q", "*:*", "fl", "id", "facet", "true", "facet.range", docValuesField, "facet.range.start", "-10", "facet.range.end", "20", "facet.range.gap", "2", "facet.range.method", "dv"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='0.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='2.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='4.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='6.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='8.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='10.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='12.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='14.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='16.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='18.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='-10.0'][.='0']");
+    
+    assertQ(req("q", "*:*", "fl", "id", "facet", "true", "facet.range", docValuesField, "facet.range.start", "0", "facet.range.end", "20", "facet.range.gap", "100"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='0.0'][.='10']");
+    
+    assertFalse(h.getCore().getLatestSchema().getField(nonDocValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(nonDocValuesField).multiValued());
+    assertTrue(h.getCore().getLatestSchema().getField(nonDocValuesField).getType() instanceof DoublePointField);
+    // Range Faceting with method = filter should work
+    assertQ(req("q", "*:*", "fl", "id", "facet", "true", "facet.range", nonDocValuesField, "facet.range.start", "-10", "facet.range.end", "20", "facet.range.gap", "2", "facet.range.method", "filter"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='0.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='2.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='4.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='6.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='8.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='10.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='12.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='14.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='16.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='18.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='-10.0'][.='0']");
+    
+    // this should actually use filter method instead of dv
+    assertQ(req("q", "*:*", "fl", "id", "facet", "true", "facet.range", nonDocValuesField, "facet.range.start", "-10", "facet.range.end", "20", "facet.range.gap", "2", "facet.range.method", "dv"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='0.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='2.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='4.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='6.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='8.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='10.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='12.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='14.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='16.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='18.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='-10.0'][.='0']");
+  }
+  
+  @Test
+  @Ignore("Enable once MultiValued docValues are supported in PointFields")
+  public void testDoublePointMultiValuedFunctionQuery() throws Exception {
+    testPointMultiValuedFunctionQuery("number_p_d_mv", "number_p_d_mv_dv", "double", getSequentialStringArrayWithDoubles(20));
+    testPointMultiValuedFunctionQuery("number_p_d_mv", "number_p_d_mv_dv", "double", getRandomStringArrayWithFloats(20, true));
+  }
+  
+  @Test
+  public void testDoublePointFieldsAtomicUpdates() throws Exception {
+    if (!Boolean.getBoolean("enable.update.log")) {
+      return;
+    }
+    doTestFloatPointFieldsAtomicUpdates("number_p_d", "double");
+    doTestFloatPointFieldsAtomicUpdates("number_p_d_dv", "double");
+    doTestFloatPointFieldsAtomicUpdates("number_p_d_dv_ns", "double");
+  }
+  
+  private void doTestFloatPointFieldsAtomicUpdates(String field, String type) throws Exception {
+    assertU(adoc(sdoc("id", "1", field, "1.1234")));
+    assertU(commit());
+
+    assertU(adoc(sdoc("id", "1", field, ImmutableMap.of("inc", 1.1F))));
+    assertU(commit());
+
+    assertQ(req("q", "id:1"),
+        "//result/doc[1]/" + type + "[@name='" + field + "'][.='2.2234']");
+    
+    assertU(adoc(sdoc("id", "1", field, ImmutableMap.of("inc", -1.1F))));
+    assertU(commit());
+    
+    // TODO: can this test be better?
+    assertQ(req("q", "id:1"),
+        "//result/doc[1]/" + type + "[@name='" + field + "'][.>'1.1233']",
+        "//result/doc[1]/" + type + "[@name='" + field + "'][.<'1.1235']");
+    
+    assertU(adoc(sdoc("id", "1", field, ImmutableMap.of("set", 3.123F))));
+    assertU(commit());
+    
+    assertQ(req("q", "id:1"),
+        "//result/doc[1]/" + type + "[@name='" + field + "'][.='3.123']");
+    
+    assertU(adoc(sdoc("id", "1", field, ImmutableMap.of("set", 3.14F))));
+    assertU(commit());
+    assertU(adoc(sdoc("id", "1", field, ImmutableMap.of("inc", 1F))));
+    assertU(commit());
+    assertQ(req("q", "id:1"),
+        "//result/doc[1]/" + type + "[@name='" + field + "'][.>'4.13']",
+        "//result/doc[1]/" + type + "[@name='" + field + "'][.<'4.15']");
+  }
+  
+  @Test
+  public void testDoublePointSetQuery() throws Exception {
+    doTestSetQueries("number_p_d", getRandomStringArrayWithDoubles(10, false), false);
+    doTestSetQueries("number_p_d_mv", getRandomStringArrayWithDoubles(10, false), true);
+    doTestSetQueries("number_p_d_ni_dv", getRandomStringArrayWithDoubles(10, false), false);
+  }
+  
+  // Float
+  
+
+  @Test
+  public void testFloatPointFieldExactQuery() throws Exception {
+    doTestFloatPointFieldExactQuery("number_p_f");
+    doTestFloatPointFieldExactQuery("number_p_f_mv");
+    doTestFloatPointFieldExactQuery("number_p_f_ni_dv");
+//    doTestFloatPointFieldExactQuery("number_p_f_ni_mv_dv");
+  }
+  
+  @Test
+  public void testFloatPointFieldReturn() throws Exception {
+    testPointFieldReturn("number_p_f", "float", new String[]{"0.0", "-1.2", "2.5", "3.02", "0.43", "5.2", "6.01", "74.0", "80.0", "9.9"});
+    clearIndex();
+    assertU(commit());
+    testPointFieldReturn("number_p_f_dv_ns", "float", new String[]{"0.0", "-1.2", "2.5", "3.02", "0.43", "5.2", "6.01", "74.0", "80.0", "9.9"});
+    clearIndex();
+    assertU(commit());
+    String[] arr = new String[atLeast(10)];
+    for (int i = 0; i < arr.length; i++) {
+      float rand = random().nextFloat() * 10;
+      arr[i] = String.valueOf(rand);
+    }
+    testPointFieldReturn("number_p_f", "float", arr);
+  }
+  
+  @Test
+  public void testFloatPointFieldRangeQuery() throws Exception {
+    doTestFloatPointFieldRangeQuery("number_p_f", "float", false);
+  }
+  
+  @Test
+  public void testFloatPointFieldSort() throws Exception {
+    String[] arr = getRandomStringArrayWithFloats(10, true);
+    doTestPointFieldSort("number_p_f", "number_p_f_dv", arr);
+  }
+  
+  @Test
+  public void testFloatPointFieldFacetField() throws Exception {
+    testPointFieldFacetField("number_p_f", "number_p_f_dv", getSequentialStringArrayWithDoubles(10));
+    clearIndex();
+    assertU(commit());
+    testPointFieldFacetField("number_p_f", "number_p_f_dv", getRandomStringArrayWithFloats(10, false));
+  }
+
+  @Test
+  public void testFloatPointFieldRangeFacet() throws Exception {
+    doTestFloatPointFieldRangeFacet("number_p_f_dv", "number_p_f");
+  }
+
+  @Test
+  public void testFloatPointFunctionQuery() throws Exception {
+    doTestFloatPointFunctionQuery("number_p_f_dv", "number_p_f", "float");
+  }
+  
+  @Test
+  public void testFloatPointStats() throws Exception {
+    testPointStats("number_p_f", "number_p_f_dv", new String[]{"-10.0", "1.1", "2.2", "3.3", "4.4", "5.5", "6.6", "7.7", "8.8", "9.9"},
+        -10D, 9.9D, "10", "1", 1E-6D);
+  }
+  
+  @Test
+  public void testFloatPointFieldMultiValuedExactQuery() throws Exception {
+    testPointFieldMultiValuedExactQuery("number_p_f_mv", getRandomStringArrayWithFloats(20, false));
+  }
+  
+  @Test
+  public void testFloatPointFieldMultiValuedReturn() throws Exception {
+    testPointFieldMultiValuedReturn("number_p_f_mv", "float", getSequentialStringArrayWithDoubles(20));
+  }
+  
+  @Test
+  public void testFloatPointFieldMultiValuedRangeQuery() throws Exception {
+    testPointFieldMultiValuedRangeQuery("number_p_f_mv", "float", getSequentialStringArrayWithDoubles(20));
+  }
+  
+  @Test
+  public void testFloatPointFieldsAtomicUpdates() throws Exception {
+    if (!Boolean.getBoolean("enable.update.log")) {
+      return;
+    }
+    doTestFloatPointFieldsAtomicUpdates("number_p_f", "float");
+    doTestFloatPointFieldsAtomicUpdates("number_p_f_dv", "float");
+    doTestFloatPointFieldsAtomicUpdates("number_p_f_dv_ns", "float");
+  }
+  
+
+  @Test
+  public void testFloatPointSetQuery() throws Exception {
+    doTestSetQueries("number_p_f", getRandomStringArrayWithFloats(10, false), false);
+    doTestSetQueries("number_p_f_mv", getRandomStringArrayWithFloats(10, false), true);
+    doTestSetQueries("number_p_f_ni_dv", getRandomStringArrayWithFloats(10, false), false);
+  }
+  
+  // Long
+  
+  @Test
+  public void testLongPointFieldExactQuery() throws Exception {
+    doTestIntPointFieldExactQuery("number_p_l", true);
+    doTestIntPointFieldExactQuery("number_p_l_mv", true);
+    doTestIntPointFieldExactQuery("number_p_l_ni_dv", true);
+//    doTestIntPointFieldExactQuery("number_p_i_ni_mv_dv", true);
+  }
+  
+  @Test
+  public void testLongPointFieldReturn() throws Exception {
+    testPointFieldReturn("number_p_l", "long", new String[]{"0", "-1", "2", "3", "43", "52", "-60", "74", "80", "99", String.valueOf(Long.MAX_VALUE)});
+    clearIndex();
+    assertU(commit());
+    testPointFieldReturn("number_p_l_dv_ns", "long", new String[]{"0", "-1", "2", "3", "43", "52", "-60", "74", "80", "99", String.valueOf(Long.MAX_VALUE)});
+  }
+  
+  @Test
+  public void testLongPointFieldRangeQuery() throws Exception {
+    doTestIntPointFieldRangeQuery("number_p_l", "long", true);
+  }
+  
+  @Test
+  public void testLongPointFieldSort() throws Exception {
+    doTestPointFieldSort("number_p_l", "number_p_l_dv", new String[]{String.valueOf(Integer.MIN_VALUE), 
+        "1", "2", "3", "4", "5", "6", "7", 
+        String.valueOf(Integer.MAX_VALUE), String.valueOf(Long.MAX_VALUE)});
+  }
+  
+  @Test
+  public void testLongPointFieldFacetField() throws Exception {
+    testPointFieldFacetField("number_p_l", "number_p_l_dv", getSequentialStringArrayWithInts(10));
+    clearIndex();
+    assertU(commit());
+    testPointFieldFacetField("number_p_l", "number_p_l_dv", getRandomStringArrayWithLongs(10, true));
+  }
+  
+  @Test
+  public void testLongPointFieldRangeFacet() throws Exception {
+    doTestIntPointFieldRangeFacet("number_p_l_dv", "number_p_l");
+  }
+  
+  @Test
+  public void testLongPointFunctionQuery() throws Exception {
+    doTestIntPointFunctionQuery("number_p_l_dv", "number_p_l", "long");
+  }
+  
+  @Test
+  public void testLongPointStats() throws Exception {
+    testPointStats("number_p_l", "number_p_l_dv", new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"},
+        0D, 9D, "10", "1", 0D);
+  }
+  
+  @Test
+  public void testLongPointFieldMultiValuedExactQuery() throws Exception {
+    testPointFieldMultiValuedExactQuery("number_p_l_mv", getSequentialStringArrayWithInts(20));
+  }
+  
+  @Test
+  public void testLongPointFieldMultiValuedReturn() throws Exception {
+    testPointFieldMultiValuedReturn("number_p_l_mv", "long", getSequentialStringArrayWithInts(20));
+  }
+  
+  @Test
+  public void testLongPointFieldMultiValuedRangeQuery() throws Exception {
+    testPointFieldMultiValuedRangeQuery("number_p_l_mv", "long", getSequentialStringArrayWithInts(20));
+  }
+  
+  @Test
+  public void testLongPointFieldsAtomicUpdates() throws Exception {
+    if (!Boolean.getBoolean("enable.update.log")) {
+      return;
+    }
+    testIntPointFieldsAtomicUpdates("number_p_l", "long");
+    testIntPointFieldsAtomicUpdates("number_p_l_dv", "long");
+    testIntPointFieldsAtomicUpdates("number_p_l_dv_ns", "long");
+  }
+  
+  @Test
+  public void testLongPointSetQuery() throws Exception {
+    doTestSetQueries("number_p_l", getRandomStringArrayWithLongs(10, false), false);
+    doTestSetQueries("number_p_l_mv", getRandomStringArrayWithLongs(10, false), true);
+    doTestSetQueries("number_p_l_ni_dv", getRandomStringArrayWithLongs(10, false), false);
+  }
+  
+  // Helper methods
+  
+  private String[] getRandomStringArrayWithDoubles(int length, boolean sorted) {
+    Set<Double> set;
+    if (sorted) {
+      set = new TreeSet<>();
+    } else {
+      set = new HashSet<>();
+    }
+    while (set.size() < length) {
+      double f = random().nextDouble() * (Double.MAX_VALUE/2);
+      if (random().nextBoolean()) {
+        f = f * -1;
+      }
+      set.add(f);
+    }
+    String[] stringArr = new String[length];
+    int i = 0;
+    for (double val:set) {
+      stringArr[i] = String.valueOf(val);
+      i++;
+    }
+    return stringArr;
+  }
+  
+  private String[] getRandomStringArrayWithFloats(int length, boolean sorted) {
+    Set<Float> set;
+    if (sorted) {
+      set = new TreeSet<>();
+    } else {
+      set = new HashSet<>();
+    }
+    while (set.size() < length) {
+      float f = random().nextFloat() * (Float.MAX_VALUE/2);
+      if (random().nextBoolean()) {
+        f = f * -1;
+      }
+      set.add(f);
+    }
+    String[] stringArr = new String[length];
+    int i = 0;
+    for (float val:set) {
+      stringArr[i] = String.valueOf(val);
+      i++;
+    }
+    return stringArr;
+  }
+  
+  private String[] getSequentialStringArrayWithInts(int length) {
+    String[] arr = new String[length];
+    for (int i = 0; i < length; i++) {
+      arr[i] = String.valueOf(i);
+    }
+    return arr;
+  }
+  
+  private String[] getSequentialStringArrayWithDoubles(int length) {
+    String[] arr = new String[length];
+    for (int i = 0; i < length; i++) {
+      arr[i] = String.format(Locale.ROOT, "%d.0", i);
+    }
+    return arr;
+  }
+  
+  private String[] getRandomStringArrayWithInts(int length, boolean sorted) {
+    Set<Integer> set;
+    if (sorted) {
+      set = new TreeSet<>();
+    } else {
+      set = new HashSet<>();
+    }
+    while (set.size() < length) {
+      int number = random().nextInt(100);
+      if (random().nextBoolean()) {
+        number = number * -1;
+      }
+      set.add(number);
+    }
+    String[] stringArr = new String[length];
+    int i = 0;
+    for (int val:set) {
+      stringArr[i] = String.valueOf(val);
+      i++;
+    }
+    return stringArr;
+  }
+  
+  private String[] getRandomStringArrayWithLongs(int length, boolean sorted) {
+    Set<Long> set;
+    if (sorted) {
+      set = new TreeSet<>();
+    } else {
+      set = new HashSet<>();
+    }
+    while (set.size() < length) {
+      long number = random().nextLong();
+      if (random().nextBoolean()) {
+        number = number * -1;
+      }
+      set.add(number);
+    }
+    String[] stringArr = new String[length];
+    int i = 0;
+    for (long val:set) {
+      stringArr[i] = String.valueOf(val);
+      i++;
+    }
+    return stringArr;
+  }
+  
+  private void doTestIntPointFieldExactQuery(String field, boolean testLong) throws Exception {
+    for (int i=0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), field, String.valueOf(i+1)));
+    }
+    assertU(commit());
+    for (int i = 0; i < 10; i++) {
+      assertQ(req("q", field + ":"+(i+1), "fl", "id, " + field), 
+          "//*[@numFound='1']");
+    }
+    
+    for (int i = 0; i < 10; i++) {
+      assertQ(req("q", field + ":" + (i+1) + " OR " + field + ":" + ((i+1)%10 + 1)), "//*[@numFound='2']");
+    }
+    
+    assertU(adoc("id", String.valueOf(Integer.MAX_VALUE), field, String.valueOf(Integer.MAX_VALUE)));
+    assertU(commit());
+    assertQ(req("q", field + ":"+Integer.MAX_VALUE, "fl", "id, " + field), 
+        "//*[@numFound='1']");
+    
+    if (testLong) {
+      for (long i = (long)Integer.MAX_VALUE; i < (long)Integer.MAX_VALUE + 10; i++) {
+        assertU(adoc("id", String.valueOf(i), field, String.valueOf(i+1)));
+      }
+      assertU(commit());
+      for (long i = (long)Integer.MAX_VALUE; i < (long)Integer.MAX_VALUE + 10; i++) {
+        assertQ(req("q", field + ":"+(i+1), "fl", "id, " + field), 
+            "//*[@numFound='1']");
+      }
+      assertU(adoc("id", String.valueOf(Long.MAX_VALUE), field, String.valueOf(Long.MAX_VALUE)));
+      assertU(commit());
+      assertQ(req("q", field + ":"+Long.MAX_VALUE, "fl", "id, " + field), 
+          "//*[@numFound='1']");
+    }
+    
+    clearIndex();
+    assertU(commit());
+  }
+
+  private void testPointFieldReturn(String field, String type, String[] values) throws Exception {
+    SchemaField sf = h.getCore().getLatestSchema().getField(field);
+    assert sf.stored() || (sf.hasDocValues() && sf.useDocValuesAsStored()): 
+      "Unexpected field definition for " + field; 
+    for (int i=0; i < values.length; i++) {
+      assertU(adoc("id", String.valueOf(i), field, values[i]));
+    }
+    assertU(commit());
+    String[] expected = new String[values.length + 1];
+    expected[0] = "//*[@numFound='" + values.length + "']"; 
+    for (int i = 1; i <= values.length; i++) {
+      expected[i] = "//result/doc[" + i + "]/" + type + "[@name='" + field + "'][.='" + values[i-1] + "']";
+    }
+    assertQ(req("q", "*:*", "fl", "id, " + field, "rows", String.valueOf(values.length)), expected);
+  }
+
+  private void doTestIntPointFieldRangeQuery(String fieldName, String type, boolean testLong) throws Exception {
+    for (int i = 0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), fieldName, String.valueOf(i)));
+    }
+    assertU(commit());
+    assertQ(req("q", fieldName + ":[0 TO 3]", "fl", "id, " + fieldName), 
+        "//*[@numFound='4']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='0']",
+        "//result/doc[2]/" + type + "[@name='" + fieldName + "'][.='1']",
+        "//result/doc[3]/" + type + "[@name='" + fieldName + "'][.='2']",
+        "//result/doc[4]/" + type + "[@name='" + fieldName + "'][.='3']");
+    
+    assertQ(req("q", fieldName + ":{0 TO 3]", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='1']",
+        "//result/doc[2]/" + type + "[@name='" + fieldName + "'][.='2']",
+        "//result/doc[3]/" + type + "[@name='" + fieldName + "'][.='3']");
+    
+    assertQ(req("q", fieldName + ":[0 TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='0']",
+        "//result/doc[2]/" + type + "[@name='" + fieldName + "'][.='1']",
+        "//result/doc[3]/" + type + "[@name='" + fieldName + "'][.='2']");
+    
+    assertQ(req("q", fieldName + ":{0 TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='2']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='1']",
+        "//result/doc[2]/" + type + "[@name='" + fieldName + "'][.='2']");
+    
+    assertQ(req("q", fieldName + ":{0 TO *}", "fl", "id, " + fieldName), 
+        "//*[@numFound='9']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='1']");
+    
+    assertQ(req("q", fieldName + ":{* TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='0']");
+    
+    assertQ(req("q", fieldName + ":[* TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='0']");
+    
+    assertQ(req("q", fieldName + ":[* TO *}", "fl", "id, " + fieldName), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='0']",
+        "//result/doc[10]/" + type + "[@name='" + fieldName + "'][.='9']");
+    
+    clearIndex();
+    assertU(commit());
+    
+    String[] arr;
+    if (testLong) {
+      arr = getRandomStringArrayWithLongs(10, true);
+    } else {
+      arr = getRandomStringArrayWithInts(10, true);
+    }
+    for (int i = 0; i < arr.length; i++) {
+      assertU(adoc("id", String.valueOf(i), fieldName, arr[i]));
+    }
+    assertU(commit());
+    for (int i = 0; i < arr.length; i++) {
+      assertQ(req("q", fieldName + ":[" + arr[0] + " TO " + arr[i] + "]", "fl", "id, " + fieldName), 
+          "//*[@numFound='" + (i + 1) + "']");
+      assertQ(req("q", fieldName + ":{" + arr[0] + " TO " + arr[i] + "}", "fl", "id, " + fieldName), 
+          "//*[@numFound='" + (Math.max(0,  i-1)) + "']");
+    }
+  }
+  
+  private void testPointFieldFacetField(String nonDocValuesField, String docValuesField, String[] numbers) throws Exception {
+    assert numbers != null && numbers.length == 10;
+    
+    assertFalse(h.getCore().getLatestSchema().getField(docValuesField).multiValued());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).getType() instanceof PointField);
+    
+    for (int i = 0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), docValuesField, numbers[i], nonDocValuesField, numbers[i]));
+    }
+    assertU(commit());
+    assertQ(req("q", "*:*", "fl", "id, " + docValuesField, "facet", "true", "facet.field", docValuesField), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + docValuesField +"']/int[@name='" + numbers[1] + "'][.='1']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + docValuesField +"']/int[@name='" + numbers[2] + "'][.='1']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + docValuesField +"']/int[@name='" + numbers[3] + "'][.='1']");
+    
+    assertU(adoc("id", "10", docValuesField, numbers[1], nonDocValuesField, numbers[1]));
+    
+    assertU(commit());
+    assertQ(req("q", "*:*", "fl", "id, " + docValuesField, "facet", "true", "facet.field", docValuesField), 
+        "//*[@numFound='11']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + docValuesField +"']/int[@name='" + numbers[1] + "'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + docValuesField +"']/int[@name='" + numbers[2] + "'][.='1']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + docValuesField +"']/int[@name='" + numbers[3] + "'][.='1']");
+    
+    assertFalse(h.getCore().getLatestSchema().getField(nonDocValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(nonDocValuesField).getType() instanceof PointField);
+    assertQEx("Expecting Exception", 
+        "Can't facet on a PointField without docValues", 
+        req("q", "*:*", "fl", "id, " + nonDocValuesField, "facet", "true", "facet.field", nonDocValuesField), 
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+  
+  private void doTestIntPointFieldRangeFacet(String docValuesField, String nonDocValuesField) throws Exception {
+    for (int i = 0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), docValuesField, String.valueOf(i), nonDocValuesField, String.valueOf(i)));
+    }
+    assertU(commit());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).getType() instanceof PointField);
+    assertQ(req("q", "*:*", "facet", "true", "facet.range", docValuesField, "facet.range.start", "-10", "facet.range.end", "10", "facet.range.gap", "2"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='2'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='4'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='6'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='8'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='-10'][.='0']");
+    
+    assertQ(req("q", "*:*", "facet", "true", "facet.range", docValuesField, "facet.range.start", "-10", "facet.range.end", "10", "facet.range.gap", "2", "facet.range.method", "dv"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='2'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='4'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='6'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='8'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='-10'][.='0']");
+    
+    assertFalse(h.getCore().getLatestSchema().getField(nonDocValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(nonDocValuesField).getType() instanceof PointField);
+    // Range Faceting with method = filter should work
+    assertQ(req("q", "*:*", "facet", "true", "facet.range", nonDocValuesField, "facet.range.start", "-10", "facet.range.end", "10", "facet.range.gap", "2", "facet.range.method", "filter"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='2'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='4'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='6'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='8'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='-10'][.='0']");
+    
+    // this should actually use filter method instead of dv
+    assertQ(req("q", "*:*", "facet", "true", "facet.range", nonDocValuesField, "facet.range.start", "-10", "facet.range.end", "10", "facet.range.gap", "2", "facet.range.method", "dv"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='2'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='4'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='6'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='8'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='-10'][.='0']");
+  }
+  
+  private void doTestIntPointFunctionQuery(String dvFieldName, String nonDvFieldName, String type) throws Exception {
+    for (int i = 0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), dvFieldName, String.valueOf(i), nonDvFieldName, String.valueOf(i)));
+    }
+    assertU(commit());
+    assertTrue(h.getCore().getLatestSchema().getField(dvFieldName).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(dvFieldName).getType() instanceof PointField);
+    assertQ(req("q", "*:*", "fl", "id, " + dvFieldName, "sort", "product(-1," + dvFieldName + ") asc"), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/" + type + "[@name='" + dvFieldName + "'][.='9']",
+        "//result/doc[2]/" + type + "[@name='" + dvFieldName + "'][.='8']",
+        "//result/doc[3]/" + type + "[@name='" + dvFieldName + "'][.='7']",
+        "//result/doc[10]/" + type + "[@name='" + dvFieldName + "'][.='0']");
+    
+    assertQ(req("q", "*:*", "fl", "id, " + dvFieldName + ", product(-1," + dvFieldName + ")"), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/float[@name='product(-1," + dvFieldName + ")'][.='-0.0']",
+        "//result/doc[2]/float[@name='product(-1," + dvFieldName + ")'][.='-1.0']",
+        "//result/doc[3]/float[@name='product(-1," + dvFieldName + ")'][.='-2.0']",
+        "//result/doc[10]/float[@name='product(-1," + dvFieldName + ")'][.='-9.0']");
+    
+    assertQ(req("q", "*:*", "fl", "id, " + dvFieldName + ", field(" + dvFieldName + ")"), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/" + type + "[@name='field(" + dvFieldName + ")'][.='0']",
+        "//result/doc[2]/" + type + "[@name='field(" + dvFieldName + ")'][.='1']",
+        "//result/doc[3]/" + type + "[@name='field(" + dvFieldName + ")'][.='2']",
+        "//result/doc[10]/" + type + "[@name='field(" + dvFieldName + ")'][.='9']");
+    
+    assertFalse(h.getCore().getLatestSchema().getField(nonDvFieldName).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(nonDvFieldName).getType() instanceof PointField);
+
+    assertQEx("Expecting Exception", 
+        "sort param could not be parsed as a query", 
+        req("q", "*:*", "fl", "id, " + nonDvFieldName, "sort", "product(-1," + nonDvFieldName + ") asc"), 
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+  
+  private void testPointStats(String field, String dvField, String[] numbers, double min, double max, String count, String missing, double delta) {
+    String minMin = String.valueOf(min - Math.abs(delta*min));
+    String maxMin = String.valueOf(min + Math.abs(delta*min));
+    String minMax = String.valueOf(max - Math.abs(delta*max));
+    String maxMax = String.valueOf(max + Math.abs(delta*max));
+    for (int i = 0; i < numbers.length; i++) {
+      assertU(adoc("id", String.valueOf(i), dvField, numbers[i], field, numbers[i]));
+    }
+    assertU(adoc("id", String.valueOf(numbers.length)));
+    assertU(commit());
+    assertTrue(h.getCore().getLatestSchema().getField(dvField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(dvField).getType() instanceof PointField);
+    assertQ(req("q", "*:*", "fl", "id, " + dvField, "stats", "true", "stats.field", dvField), 
+        "//*[@numFound='11']",
+        "//lst[@name='stats']/lst[@name='stats_fields']/lst[@name='" + dvField+ "']/double[@name='min'][.>='" + minMin + "']",
+        "//lst[@name='stats']/lst[@name='stats_fields']/lst[@name='" + dvField+ "']/double[@name='min'][.<='" + maxMin+ "']",
+        "//lst[@name='stats']/lst[@name='stats_fields']/lst[@name='" + dvField+ "']/double[@name='max'][.>='" + minMax + "']",
+        "//lst[@name='stats']/lst[@name='stats_fields']/lst[@name='" + dvField+ "']/double[@name='max'][.<='" + maxMax + "']",
+        "//lst[@name='stats']/lst[@name='stats_fields']/lst[@name='" + dvField+ "']/long[@name='count'][.='" + count + "']",
+        "//lst[@name='stats']/lst[@name='stats_fields']/lst[@name='" + dvField+ "']/long[@name='missing'][.='" + missing + "']");
+    
+    assertFalse(h.getCore().getLatestSchema().getField(field).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(field).getType() instanceof PointField);
+    assertQEx("Expecting Exception", 
+        "Can't calculate stats on a PointField without docValues", 
+        req("q", "*:*", "fl", "id, " + field, "stats", "true", "stats.field", field), 
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+  
+  private void testPointFieldMultiValuedExactQuery(String fieldName, String[] numbers) throws Exception {
+    assert numbers != null && numbers.length == 20;
+    assertTrue(h.getCore().getLatestSchema().getField(fieldName).multiValued());
+    assertTrue(h.getCore().getLatestSchema().getField(fieldName).getType() instanceof PointField);
+    for (int i=0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), fieldName, numbers[i], fieldName, numbers[i+10]));
+    }
+    assertU(commit());
+    for (int i = 0; i < 20; i++) {
+      assertQ(req("q", fieldName + ":" + numbers[i].replace("-", "\\-")), 
+          "//*[@numFound='1']");
+    }
+    
+    for (int i = 0; i < 20; i++) {
+      assertQ(req("q", fieldName + ":" + numbers[i].replace("-", "\\-") + " OR " + fieldName + ":" + numbers[(i+1)%10].replace("-", "\\-")), "//*[@numFound='2']");
+    }
+  }
+  
+  private void testPointFieldMultiValuedReturn(String fieldName, String type, String[] numbers) throws Exception {
+    assert numbers != null && numbers.length == 20;
+    assertTrue(h.getCore().getLatestSchema().getField(fieldName).multiValued());
+    assertTrue(h.getCore().getLatestSchema().getField(fieldName).getType() instanceof PointField);
+    for (int i=0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), fieldName, numbers[i], fieldName, numbers[i+10]));
+    }
+    assertU(commit());
+    String[] expected = new String[11];
+    String[] expected2 = new String[11];
+    expected[0] = "//*[@numFound='10']"; 
+    expected2[0] = "//*[@numFound='10']"; 
+    for (int i = 1; i <= 10; i++) {
+      expected[i] = "//result/doc[" + i + "]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[i-1] + "']";
+      expected2[i] = "//result/doc[" + i + "]/arr[@name='" + fieldName + "']/" + type + "[2][.='" + numbers[i + 9] + "']";
+    }
+    assertQ(req("q", "*:*", "fl", "id, " + fieldName), expected);
+    assertQ(req("q", "*:*", "fl", "id, " + fieldName), expected2);
+  }
+  
+  private void testPointFieldMultiValuedRangeQuery(String fieldName, String type, String[] numbers) throws Exception {
+    assert numbers != null && numbers.length == 20;
+    assertTrue(h.getCore().getLatestSchema().getField(fieldName).multiValued());
+    assertTrue(h.getCore().getLatestSchema().getField(fieldName).getType() instanceof PointField);
+    for (int i=0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), fieldName, String.valueOf(i), fieldName, String.valueOf(i+10)));
+    }
+    assertU(commit());
+    assertQ(req("q", fieldName + ":[0 TO 3]", "fl", "id, " + fieldName), 
+        "//*[@numFound='4']",
+        "//result/doc[1]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[0] + "']",
+        "//result/doc[1]/arr[@name='" + fieldName + "']/" + type + "[2][.='" + numbers[10] + "']",
+        "//result/doc[2]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[1] + "']",
+        "//result/doc[2]/arr[@name='" + fieldName + "']/" + type + "[2][.='" + numbers[11] + "']",
+        "//result/doc[3]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[2] + "']",
+        "//result/doc[3]/arr[@name='" + fieldName + "']/" + type + "[2][.='" + numbers[12] + "']",
+        "//result/doc[4]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[3] + "']",
+        "//result/doc[4]/arr[@name='" + fieldName + "']/" + type + "[2][.='" + numbers[13] + "']");
+    
+    assertQ(req("q", fieldName + ":{0 TO 3]", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[1] + "']",
+        "//result/doc[2]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[2] + "']",
+        "//result/doc[3]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[3] + "']");
+    
+    assertQ(req("q", fieldName + ":[0 TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[0] + "']",
+        "//result/doc[2]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[1] + "']",
+        "//result/doc[3]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[2] + "']");
+    
+    assertQ(req("q", fieldName + ":{0 TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='2']",
+        "//result/doc[1]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[1] + "']",
+        "//result/doc[2]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[2] + "']");
+    
+    assertQ(req("q", fieldName + ":{0 TO *}", "fl", "id, " + fieldName), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[0] + "']");
+    
+    assertQ(req("q", fieldName + ":{10 TO *}", "fl", "id, " + fieldName), 
+        "//*[@numFound='9']",
+        "//result/doc[1]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[1] + "']");
+    
+    assertQ(req("q", fieldName + ":{* TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[0] + "']");
+    
+    assertQ(req("q", fieldName + ":[* TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[0] + "']");
+    
+    assertQ(req("q", fieldName + ":[* TO *}", "fl", "id, " + fieldName), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[0] + "']",
+        "//result/doc[10]/arr[@name='" + fieldName + "']/" + type + "[1][.='" + numbers[9] + "']");
+  }
+
+  private void testPointFieldMultiValuedFacetField(String nonDocValuesField, String dvFieldName, String[] numbers) throws Exception {
+    assert numbers != null && numbers.length == 20;
+    assertTrue(h.getCore().getLatestSchema().getField(dvFieldName).multiValued());
+    assertTrue(h.getCore().getLatestSchema().getField(dvFieldName).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(dvFieldName).getType() instanceof PointField);
+    
+    for (int i = 0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), dvFieldName, numbers[i], dvFieldName, numbers[i + 10], 
+          nonDocValuesField, numbers[i], nonDocValuesField, numbers[i + 10]));
+    }
+    assertU(commit());
+    
+    assertQ(req("q", "*:*", "fl", "id, " + dvFieldName, "facet", "true", "facet.field", dvFieldName), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + dvFieldName +"']/int[@name='" + numbers[1] + "'][.='1']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + dvFieldName +"']/int[@name='" + numbers[2] + "'][.='1']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + dvFieldName +"']/int[@name='" + numbers[3] + "'][.='1']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + dvFieldName +"']/int[@name='" + numbers[10] + "'][.='1']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + dvFieldName +"']/int[@name='" + numbers[11] + "'][.='1']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + dvFieldName +"']/int[@name='" + numbers[12] + "'][.='1']");
+    
+    assertU(adoc("id", "10", dvFieldName, numbers[1], nonDocValuesField, numbers[1]));
+    
+    assertU(commit());
+    assertQ(req("q", "*:*", "fl", "id, " + dvFieldName, "facet", "true", "facet.field", dvFieldName), 
+        "//*[@numFound='11']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + dvFieldName +"']/int[@name='" + numbers[1] + "'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + dvFieldName +"']/int[@name='" + numbers[2] + "'][.='1']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + dvFieldName +"']/int[@name='" + numbers[3] + "'][.='1']",
+        "//lst[@name='facet_counts']/lst[@name='facet_fields']/lst[@name='" + dvFieldName +"']/int[@name='" + numbers[10] + "'][.='1']");
+    
+    assertFalse(h.getCore().getLatestSchema().getField(nonDocValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(nonDocValuesField).getType() instanceof PointField);
+    assertQEx("Expecting Exception", 
+        "Can't facet on a PointField without docValues", 
+        req("q", "*:*", "fl", "id, " + nonDocValuesField, "facet", "true", "facet.field", nonDocValuesField), 
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  private void testPointMultiValuedFunctionQuery(String nonDocValuesField, String docValuesField, String type, String[] numbers) throws Exception {
+    assert numbers != null && numbers.length == 20;
+    for (int i = 0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), docValuesField, numbers[i], docValuesField, numbers[i+10], 
+          nonDocValuesField, numbers[i], nonDocValuesField, numbers[i+10]));
+    }
+    assertU(commit());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).multiValued());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).getType() instanceof PointField);
+    String function = "field(" + docValuesField + ", min)";
+    
+    assertQ(req("q", "*:*", "fl", "id, " + function), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/" + type + "[@name='" + function + "'][.='" + numbers[0] + "']",
+        "//result/doc[2]/" + type + "[@name='" + function + "'][.='" + numbers[1] + "']",
+        "//result/doc[3]/" + type + "[@name='" + function + "'][.='" + numbers[2] + "']",
+        "//result/doc[10]/" + type + "[@name='" + function + "'][.='" + numbers[9] + "']");
+    
+//    if (dvIsRandomAccessOrds(docValuesField)) {
+//      function = "field(" + docValuesField + ", max)";
+//      assertQ(req("q", "*:*", "fl", "id, " + function), 
+//          "//*[@numFound='10']",
+//          "//result/doc[1]/int[@name='" + function + "'][.='10']",
+//          "//result/doc[2]/int[@name='" + function + "'][.='11']",
+//          "//result/doc[3]/int[@name='" + function + "'][.='12']",
+//          "//result/doc[10]/int[@name='" + function + "'][.='19']");
+//    }
+    
+    assertFalse(h.getCore().getLatestSchema().getField(nonDocValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(nonDocValuesField).multiValued());
+    assertTrue(h.getCore().getLatestSchema().getField(nonDocValuesField).getType() instanceof PointField);
+
+    function = "field(" + nonDocValuesField + ",min)";
+    
+    assertQEx("Expecting Exception", 
+        "sort param could not be parsed as a query", 
+        req("q", "*:*", "fl", "id", "sort", function + " desc"), 
+        SolrException.ErrorCode.BAD_REQUEST);
+    
+    assertQEx("Expecting Exception", 
+        "docValues='true' is required to select 'min' value from multivalued field (" + nonDocValuesField + ") at query time", 
+        req("q", "*:*", "fl", "id, " + function), 
+        SolrException.ErrorCode.BAD_REQUEST);
+    
+    function = "field(" + docValuesField + ",foo)";
+    assertQEx("Expecting Exception", 
+        "Multi-Valued field selector 'foo' not supported", 
+        req("q", "*:*", "fl", "id, " + function), 
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+
+  private void testIntPointFieldsAtomicUpdates(String field, String type) throws Exception {
+    assertU(adoc(sdoc("id", "1", field, "1")));
+    assertU(commit());
+
+    assertU(adoc(sdoc("id", "1", field, ImmutableMap.of("inc", 1))));
+    assertU(commit());
+
+    assertQ(req("q", "id:1"),
+        "//result/doc[1]/" + type + "[@name='" + field + "'][.='2']");
+    
+    assertU(adoc(sdoc("id", "1", field, ImmutableMap.of("inc", -1))));
+    assertU(commit());
+    
+    assertQ(req("q", "id:1"),
+        "//result/doc[1]/" + type + "[@name='" + field + "'][.='1']");
+    
+    assertU(adoc(sdoc("id", "1", field, ImmutableMap.of("set", 3))));
+    assertU(commit());
+    
+    assertQ(req("q", "id:1"),
+        "//result/doc[1]/" + type + "[@name='" + field + "'][.='3']");
+  }
+
+  private void doTestFloatPointFieldExactQuery(String field) throws Exception {
+    for (int i=0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), field, String.valueOf(i + "." + i)));
+    }
+    assertU(commit());
+    for (int i = 0; i < 9; i++) {
+      assertQ(req("q", field + ":"+(i+1) + "." + (i+1), "fl", "id, " + field), 
+          "//*[@numFound='1']");
+    }
+    
+    for (int i = 0; i < 9; i++) {
+      String num1 = (i+1) + "." + (i+1);
+      String num2 = ((i+1)%9 + 1) + "." + ((i+1)%9 + 1);
+      assertQ(req("q", field + ":" + num1 + " OR " + field + ":" + num2), "//*[@numFound='2']");
+    }
+    
+    clearIndex();
+    assertU(commit());
+    for (int i = 0; i < atLeast(10); i++) {
+      float rand = random().nextFloat() * 10;
+      assertU(adoc("id", "random_number ", field, String.valueOf(rand))); //always the same id to override
+      assertU(commit());
+      assertQ(req("q", field + ":" + rand, "fl", "id, " + field), 
+          "//*[@numFound='1']");
+    }
+    clearIndex();
+    assertU(commit());
+  }
+  
+  private void doTestPointFieldSort(String field, String dvField, String[] arr) throws Exception {
+    assert arr != null && arr.length == 10;
+    for (int i = 0; i < arr.length; i++) {
+      assertU(adoc("id", String.valueOf(i), dvField, String.valueOf(arr[i]), field, String.valueOf(arr[i])));
+    }
+    assertU(commit());
+    assertTrue(h.getCore().getLatestSchema().getField(dvField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(dvField).getType() instanceof PointField);
+    assertQ(req("q", "*:*", "fl", "id", "sort", dvField + " desc"), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/str[@name='id'][.='9']",
+        "//result/doc[2]/str[@name='id'][.='8']",
+        "//result/doc[3]/str[@name='id'][.='7']",
+        "//result/doc[10]/str[@name='id'][.='0']");
+    
+    assertFalse(h.getCore().getLatestSchema().getField(field).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(field).getType() instanceof PointField);
+    assertQEx("Expecting Exception", 
+        "can not sort on a PointField without doc values: " + field, 
+        req("q", "*:*", "fl", "id", "sort", field + " desc"), 
+        SolrException.ErrorCode.BAD_REQUEST);
+    
+    //TODO: sort missing
+  }
+  
+  private void doTestFloatPointFieldRangeQuery(String fieldName, String type, boolean testDouble) throws Exception {
+    for (int i = 0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), fieldName, String.valueOf(i)));
+    }
+    assertU(commit());
+    assertQ(req("q", fieldName + ":[0 TO 3]", "fl", "id, " + fieldName), 
+        "//*[@numFound='4']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='0.0']",
+        "//result/doc[2]/" + type + "[@name='" + fieldName + "'][.='1.0']",
+        "//result/doc[3]/" + type + "[@name='" + fieldName + "'][.='2.0']",
+        "//result/doc[4]/" + type + "[@name='" + fieldName + "'][.='3.0']");
+    
+    assertQ(req("q", fieldName + ":{0 TO 3]", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='1.0']",
+        "//result/doc[2]/" + type + "[@name='" + fieldName + "'][.='2.0']",
+        "//result/doc[3]/" + type + "[@name='" + fieldName + "'][.='3.0']");
+    
+    assertQ(req("q", fieldName + ":[0 TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='0.0']",
+        "//result/doc[2]/" + type + "[@name='" + fieldName + "'][.='1.0']",
+        "//result/doc[3]/" + type + "[@name='" + fieldName + "'][.='2.0']");
+    
+    assertQ(req("q", fieldName + ":{0 TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='2']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='1.0']",
+        "//result/doc[2]/" + type + "[@name='" + fieldName + "'][.='2.0']");
+    
+    assertQ(req("q", fieldName + ":{0 TO *}", "fl", "id, " + fieldName), 
+        "//*[@numFound='9']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='1.0']");
+    
+    assertQ(req("q", fieldName + ":{* TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='0.0']");
+    
+    assertQ(req("q", fieldName + ":[* TO 3}", "fl", "id, " + fieldName), 
+        "//*[@numFound='3']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='0.0']");
+    
+    assertQ(req("q", fieldName + ":[* TO *}", "fl", "id, " + fieldName), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='0.0']",
+        "//result/doc[10]/" + type + "[@name='" + fieldName + "'][.='9.0']");
+    
+    assertQ(req("q", fieldName + ":[0.9 TO 1.01]", "fl", "id, " + fieldName), 
+        "//*[@numFound='1']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='1.0']");
+    
+    assertQ(req("q", fieldName + ":{0.9 TO 1.01}", "fl", "id, " + fieldName), 
+        "//*[@numFound='1']",
+        "//result/doc[1]/" + type + "[@name='" + fieldName + "'][.='1.0']");
+    
+    clearIndex();
+    assertU(commit());
+    
+    String[] arr;
+    if (testDouble) {
+      arr = getRandomStringArrayWithDoubles(10, true);
+    } else {
+      arr = getRandomStringArrayWithFloats(10, true);
+    }
+    for (int i = 0; i < arr.length; i++) {
+      assertU(adoc("id", String.valueOf(i), fieldName, arr[i]));
+    }
+    assertU(commit());
+    for (int i = 0; i < arr.length; i++) {
+      assertQ(req("q", fieldName + ":[" + arr[0] + " TO " + arr[i] + "]", "fl", "id, " + fieldName), 
+          "//*[@numFound='" + (i + 1) + "']");
+      assertQ(req("q", fieldName + ":{" + arr[0] + " TO " + arr[i] + "}", "fl", "id, " + fieldName), 
+          "//*[@numFound='" + (Math.max(0,  i-1)) + "']");
+    }
+  }
+  
+  private void doTestFloatPointFieldRangeFacet(String docValuesField, String nonDocValuesField) throws Exception {
+    
+    for (int i = 0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), docValuesField, String.format(Locale.ROOT, "%f", (float)i*1.1), nonDocValuesField, String.format(Locale.ROOT, "%f", (float)i*1.1)));
+    }
+    assertU(commit());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(docValuesField).getType() instanceof PointField);
+    assertQ(req("q", "*:*", "facet", "true", "facet.range", docValuesField, "facet.range.start", "-10", "facet.range.end", "10", "facet.range.gap", "2"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='0.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='2.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='4.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='6.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='8.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='-10.0'][.='0']");
+    
+    assertQ(req("q", "*:*", "facet", "true", "facet.range", docValuesField, "facet.range.start", "-10", "facet.range.end", "10", "facet.range.gap", "2", "facet.range.method", "dv"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='0.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='2.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='4.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='6.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='8.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + docValuesField + "']/lst[@name='counts']/int[@name='-10.0'][.='0']");
+    
+    assertFalse(h.getCore().getLatestSchema().getField(nonDocValuesField).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(nonDocValuesField).getType() instanceof PointField);
+    // Range Faceting with method = filter should work
+    assertQ(req("q", "*:*", "facet", "true", "facet.range", nonDocValuesField, "facet.range.start", "-10", "facet.range.end", "10", "facet.range.gap", "2", "facet.range.method", "filter"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='0.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='2.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='4.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='6.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='8.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='-10.0'][.='0']");
+    
+    // this should actually use filter method instead of dv
+    assertQ(req("q", "*:*", "facet", "true", "facet.range", nonDocValuesField, "facet.range.start", "-10", "facet.range.end", "10", "facet.range.gap", "2", "facet.range.method", "dv"), 
+        "//*[@numFound='10']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='0.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='2.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='4.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='6.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='8.0'][.='2']",
+        "//lst[@name='facet_counts']/lst[@name='facet_ranges']/lst[@name='" + nonDocValuesField + "']/lst[@name='counts']/int[@name='-10.0'][.='0']");
+  }
+  
+  private void doTestFloatPointFunctionQuery(String dvFieldName, String nonDvFieldName, String type) throws Exception {
+    for (int i = 0; i < 10; i++) {
+      assertU(adoc("id", String.valueOf(i), dvFieldName, String.format(Locale.ROOT, "%f", (float)i*1.1), nonDvFieldName, String.format(Locale.ROOT, "%f", (float)i*1.1)));
+    }
+    assertU(commit());
+    assertTrue(h.getCore().getLatestSchema().getField(dvFieldName).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(dvFieldName).getType() instanceof PointField);
+    assertQ(req("q", "*:*", "fl", "id, " + dvFieldName, "sort", "product(-1," + dvFieldName + ") asc"), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/" + type + "[@name='" + dvFieldName + "'][.='9.9']",
+        "//result/doc[2]/" + type + "[@name='" + dvFieldName + "'][.='8.8']",
+        "//result/doc[3]/" + type + "[@name='" + dvFieldName + "'][.='7.7']",
+        "//result/doc[10]/" + type + "[@name='" + dvFieldName + "'][.='0.0']");
+    
+    assertQ(req("q", "*:*", "fl", "id, " + dvFieldName + ", product(-1," + dvFieldName + ")"), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/float[@name='product(-1," + dvFieldName + ")'][.='-0.0']",
+        "//result/doc[2]/float[@name='product(-1," + dvFieldName + ")'][.='-1.1']",
+        "//result/doc[3]/float[@name='product(-1," + dvFieldName + ")'][.='-2.2']",
+        "//result/doc[10]/float[@name='product(-1," + dvFieldName + ")'][.='-9.9']");
+    
+    assertQ(req("q", "*:*", "fl", "id, " + dvFieldName + ", field(" + dvFieldName + ")"), 
+        "//*[@numFound='10']",
+        "//result/doc[1]/" + type + "[@name='field(" + dvFieldName + ")'][.='0.0']",
+        "//result/doc[2]/" + type + "[@name='field(" + dvFieldName + ")'][.='1.1']",
+        "//result/doc[3]/" + type + "[@name='field(" + dvFieldName + ")'][.='2.2']",
+        "//result/doc[10]/" + type + "[@name='field(" + dvFieldName + ")'][.='9.9']");
+    
+    assertFalse(h.getCore().getLatestSchema().getField(nonDvFieldName).hasDocValues());
+    assertTrue(h.getCore().getLatestSchema().getField(nonDvFieldName).getType() instanceof PointField);
+
+    assertQEx("Expecting Exception", 
+        "sort param could not be parsed as a query", 
+        req("q", "*:*", "fl", "id, " + nonDvFieldName, "sort", "product(-1," + nonDvFieldName + ") asc"), 
+        SolrException.ErrorCode.BAD_REQUEST);
+  }
+  
+  private void doTestSetQueries(String fieldName, String[] values, boolean multiValued) {
+    for (int i = 0; i < values.length; i++) {
+      assertU(adoc("id", String.valueOf(i), fieldName, values[i]));
+    }
+    assertU(commit());
+    assertTrue(h.getCore().getLatestSchema().getField(fieldName).getType() instanceof PointField);
+    
+    for (int i = 0; i < values.length; i++) {
+      assertQ(req("q", "{!term f='" + fieldName + "'}" + values[i], "fl", "id," + fieldName), 
+          "//*[@numFound='1']");
+    }
+    
+    for (int i = 0; i < values.length; i++) {
+      assertQ(req("q", "{!terms f='" + fieldName + "'}" + values[i] + "," + values[(i + 1)%values.length], "fl", "id," + fieldName), 
+          "//*[@numFound='2']");
+    }
+    
+    if (multiValued) {
+      clearIndex();
+      assertU(commit());
+      for (int i = 0; i < values.length; i++) {
+        assertU(adoc("id", String.valueOf(i), fieldName, values[i], fieldName, values[(i+1)%values.length]));
+      }
+      assertU(commit());
+      for (int i = 0; i < values.length; i++) {
+        assertQ(req("q", "{!term f='" + fieldName + "'}" + values[i], "fl", "id," + fieldName), 
+            "//*[@numFound='2']");
+      }
+      
+      for (int i = 0; i < values.length; i++) {
+        assertQ(req("q", "{!terms f='" + fieldName + "'}" + values[i] + "," + values[(i + 1)%values.length], "fl", "id," + fieldName), 
+            "//*[@numFound='3']");
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java b/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java
index 7c5fc4a..2fca452 100644
--- a/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java
+++ b/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java
@@ -25,6 +25,7 @@ import java.util.Iterator;
 
 import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.SolrTestCaseJ4.SuppressPointFields;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
@@ -36,8 +37,9 @@ import org.junit.Test;
 
 //We want codecs that support DocValues, and ones supporting blank/empty values.
 @SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42"})
+@SuppressPointFields
 public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
-
+  
   @BeforeClass
   public static void beforeClass() throws Exception {
     initCore("solrconfig-collapseqparser.xml", "schema11.xml");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/search/TestMaxScoreQueryParser.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestMaxScoreQueryParser.java b/solr/core/src/test/org/apache/solr/search/TestMaxScoreQueryParser.java
index 6059528..610e998 100644
--- a/solr/core/src/test/org/apache/solr/search/TestMaxScoreQueryParser.java
+++ b/solr/core/src/test/org/apache/solr/search/TestMaxScoreQueryParser.java
@@ -46,7 +46,7 @@ public class TestMaxScoreQueryParser extends AbstractSolrTestCase {
     assertEquals(new BoostQuery(new TermQuery(new Term("text", "foo")), 3f), q);
 
     q = parse("price:[0 TO 10]");
-    assertTrue(q instanceof LegacyNumericRangeQuery);
+    assertTrue(q instanceof LegacyNumericRangeQuery || q instanceof PointRangeQuery);
   }
 
   @Test

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java b/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java
index f4dd449..7d135e2 100644
--- a/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java
+++ b/solr/core/src/test/org/apache/solr/search/TestRandomCollapseQParserPlugin.java
@@ -24,6 +24,7 @@ import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
 import org.apache.lucene.util.TestUtil;
 import org.apache.solr.CursorPagingTest;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.SolrTestCaseJ4.SuppressPointFields;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
 import org.apache.solr.client.solrj.response.QueryResponse;
@@ -38,6 +39,7 @@ import org.junit.BeforeClass;
 
 //We want codecs that support DocValues, and ones supporting blank/empty values.
 @SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42"})
+@SuppressPointFields
 public class TestRandomCollapseQParserPlugin extends SolrTestCaseJ4 {
 
   /** Full SolrServer instance for arbitrary introspection of response data and adding fqs */

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java b/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java
index 20c1907..37462d9 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java
@@ -23,6 +23,7 @@ import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.BoostQuery;
 import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.PointInSetQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermInSetQuery;
 import org.apache.lucene.search.TermQuery;
@@ -231,6 +232,13 @@ public class TestSolrQueryParser extends SolrTestCaseJ4 {
     qParser.setIsFilter(true); // this may change in the future
     q = qParser.getQuery();
     assertEquals(20, ((TermInSetQuery)q).getTermData().size());
+    
+    // for point fields large filter query should use PointInSetQuery
+    qParser = QParser.getParser("foo_pi:(1 2 3 4 5 6 7 8 9 10 20 19 18 17 16 15 14 13 12 11)", req);
+    qParser.setIsFilter(true); // this may change in the future
+    q = qParser.getQuery();
+    assertTrue(q instanceof PointInSetQuery);
+    assertEquals(20, ((PointInSetQuery)q).getPackedPoints().size());
 
     // a filter() clause inside a relevancy query should be able to use a TermsQuery
     qParser = QParser.getParser("foo_s:aaa filter(foo_s:(a b c d e f g h i j k l m n o p q r s t u v w x y z))", req);


[37/38] lucene-solr:jira/solr-9857: LUCENE-7645: Use JDK's Arrays.binarySearch in BaseCharFilter.

Posted by ab...@apache.org.
LUCENE-7645: Use JDK's Arrays.binarySearch in BaseCharFilter.


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

Branch: refs/heads/jira/solr-9857
Commit: a14d79366f97ffde61b56aee2e2d9123ccadc8a7
Parents: 85a05b5
Author: Adrien Grand <jp...@gmail.com>
Authored: Thu Jan 19 11:27:24 2017 +0100
Committer: Adrien Grand <jp...@gmail.com>
Committed: Thu Jan 19 11:27:24 2017 +0100

----------------------------------------------------------------------
 .../analysis/charfilter/BaseCharFilter.java     | 26 +++++---------------
 1 file changed, 6 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a14d7936/lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/BaseCharFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/BaseCharFilter.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/BaseCharFilter.java
index 48ffa48..4fba9fe 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/BaseCharFilter.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/BaseCharFilter.java
@@ -41,31 +41,17 @@ public abstract class BaseCharFilter extends CharFilter {
   /** Retrieve the corrected offset. */
   @Override
   protected int correct(int currentOff) {
-    if (offsets == null || currentOff < offsets[0]) {
+    if (offsets == null) {
       return currentOff;
     }
-    
-    int hi = size - 1;
-    if(currentOff >= offsets[hi])
-      return currentOff + diffs[hi];
 
-    int lo = 0;
-    int mid = -1;
-    
-    while (hi >= lo) {
-      mid = (lo + hi) >>> 1;
-      if (currentOff < offsets[mid])
-        hi = mid - 1;
-      else if (currentOff > offsets[mid])
-        lo = mid + 1;
-      else
-        return currentOff + diffs[mid];
+    int index = Arrays.binarySearch(offsets, 0, size, currentOff);
+    if (index < -1) {
+      index = -2 - index;
     }
 
-    if (currentOff < offsets[mid])
-      return mid == 0 ? currentOff : currentOff + diffs[mid-1];
-    else
-      return currentOff + diffs[mid];
+    final int diff = index < 0 ? 0 : diffs[index];
+    return currentOff + diff;
   }
   
   protected int getLastCumulativeDiff() {


[08/38] lucene-solr:jira/solr-9857: SOLR-9935: UnifiedHighlighter, when hl.fragsize=0 don't do fragmenting

Posted by ab...@apache.org.
SOLR-9935: UnifiedHighlighter, when hl.fragsize=0 don't do fragmenting


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

Branch: refs/heads/jira/solr-9857
Commit: ed513fdee77b95379bed8f8d5f369fb0393fd364
Parents: 43874fc
Author: David Smiley <ds...@apache.org>
Authored: Tue Jan 17 08:06:21 2017 -0500
Committer: David Smiley <ds...@apache.org>
Committed: Tue Jan 17 08:07:51 2017 -0500

----------------------------------------------------------------------
 .../org/apache/solr/highlight/UnifiedSolrHighlighter.java | 10 +++++++---
 .../apache/solr/highlight/TestUnifiedSolrHighlighter.java |  7 +++++--
 2 files changed, 12 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ed513fde/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java b/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java
index 5b59b85..2633522 100644
--- a/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java
+++ b/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java
@@ -295,6 +295,13 @@ public class UnifiedSolrHighlighter extends SolrHighlighter implements PluginInf
 
     @Override
     protected BreakIterator getBreakIterator(String field) {
+      // Use a default fragsize the same as the regex Fragmenter (original Highlighter) since we're
+      //  both likely shooting for sentence-like patterns.
+      int fragsize = params.getFieldInt(field, HighlightParams.FRAGSIZE, LuceneRegexFragmenter.DEFAULT_FRAGMENT_SIZE);
+      if (fragsize == 0) { // special value; no fragmenting
+        return new WholeBreakIterator();
+      }
+
       String language = params.getFieldParam(field, HighlightParams.BS_LANGUAGE);
       String country = params.getFieldParam(field, HighlightParams.BS_COUNTRY);
       String variant = params.getFieldParam(field, HighlightParams.BS_VARIANT);
@@ -302,9 +309,6 @@ public class UnifiedSolrHighlighter extends SolrHighlighter implements PluginInf
       String type = params.getFieldParam(field, HighlightParams.BS_TYPE);
       BreakIterator baseBI = parseBreakIterator(type, locale);
 
-      // Use a default fragsize the same as the regex Fragmenter (original Highlighter) since we're
-      //  both likely shooting for sentence-like patterns.
-      int fragsize = params.getFieldInt(field, HighlightParams.FRAGSIZE, LuceneRegexFragmenter.DEFAULT_FRAGMENT_SIZE);
       if (fragsize <= 1 || baseBI instanceof WholeBreakIterator) { // no real minimum size
         return baseBI;
       }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ed513fde/solr/core/src/test/org/apache/solr/highlight/TestUnifiedSolrHighlighter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/highlight/TestUnifiedSolrHighlighter.java b/solr/core/src/test/org/apache/solr/highlight/TestUnifiedSolrHighlighter.java
index 2eb4ba3..d452829 100644
--- a/solr/core/src/test/org/apache/solr/highlight/TestUnifiedSolrHighlighter.java
+++ b/solr/core/src/test/org/apache/solr/highlight/TestUnifiedSolrHighlighter.java
@@ -79,7 +79,7 @@ public class TestUnifiedSolrHighlighter extends SolrTestCaseJ4 {
     assertU(commit());
     assertQ("multiple snippets test",
         req("q", "text:document", "sort", "id asc", "hl", "true", "hl.snippets", "2", "hl.bs.type", "SENTENCE",
-            "hl.fragsize", "0"),
+            "hl.fragsize", "-1"),
         "count(//lst[@name='highlighting']/lst[@name='101']/arr[@name='text']/*)=2",
         "//lst[@name='highlighting']/lst[@name='101']/arr/str[1]='<em>Document</em> snippet one. '",
         "//lst[@name='highlighting']/lst[@name='101']/arr/str[2]='<em>Document</em> snippet two.'");
@@ -214,9 +214,12 @@ public class TestUnifiedSolrHighlighter extends SolrTestCaseJ4 {
   public void testBreakIteratorWhole() {
     assertU(adoc("text", "Document one has a first sentence. Document two has a second sentence.", "id", "103"));
     assertU(commit());
-    assertQ("different breakiterator", 
+    assertQ("WHOLE breakiterator",
         req("q", "text:document", "sort", "id asc", "hl", "true", "hl.bs.type", "WHOLE", "hl.fragsize", "-1"),
         "//lst[@name='highlighting']/lst[@name='103']/arr[@name='text']/str='<em>Document</em> one has a first sentence. <em>Document</em> two has a second sentence.'");
+    assertQ("hl.fragsize 0 is equivalent to WHOLE",
+        req("q", "text:document", "sort", "id asc", "hl", "true", "hl.fragsize", "0"),
+        "//lst[@name='highlighting']/lst[@name='103']/arr[@name='text']/str='<em>Document</em> one has a first sentence. <em>Document</em> two has a second sentence.'");
   }
 
   public void testFragsize() {


[27/38] lucene-solr:jira/solr-9857: SOLR-8396: Add support for PointFields in Solr

Posted by ab...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
index d8f3ae5..95c403a 100644
--- a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
+++ b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
@@ -34,6 +34,7 @@ import org.apache.lucene.util.LuceneTestCase;
 import org.apache.solr.JSONTestUtil;
 import org.apache.solr.SolrTestCaseHS;
 import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.SolrTestCaseJ4.SuppressPointFields;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.request.macro.MacroExpander;
 import org.junit.AfterClass;
@@ -41,6 +42,7 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 
 @LuceneTestCase.SuppressCodecs({"Lucene3x","Lucene40","Lucene41","Lucene42","Lucene45","Appending"})
+@SuppressPointFields
 public class TestJsonFacets extends SolrTestCaseHS {
 
   private static SolrInstances servers;  // for distributed testing

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index 61de56d..59e74d90 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -167,11 +167,18 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
   public static final String SYSTEM_PROPERTY_SOLR_TESTS_USEMERGEPOLICYFACTORY = "solr.tests.useMergePolicyFactory";
   @Deprecated // remove solr.tests.useMergePolicy use with SOLR-8668
   public static final String SYSTEM_PROPERTY_SOLR_TESTS_USEMERGEPOLICY = "solr.tests.useMergePolicy";
+  
+  /**
+   * The system property {@code "solr.tests.preferPointFields"} can be used to make tests use PointFields when possible. 
+   * PointFields will only be used if the schema used by the tests uses "${solr.tests.TYPEClass}" when defining fields. 
+   * If this environment variable is not set, those tests will use PointFields 50% of the times and TrieFields the rest.
+   */
+  public static final boolean PREFER_POINT_FIELDS = Boolean.getBoolean("solr.tests.preferPointFields");
 
   private static String coreName = DEFAULT_TEST_CORENAME;
 
   public static int DEFAULT_CONNECTION_TIMEOUT = 60000;  // default socket connection timeout in ms
-
+  
   protected void writeCoreProperties(Path coreDirectory, String corename) throws IOException {
     Properties props = new Properties();
     props.setProperty("name", corename);
@@ -215,6 +222,19 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
     public String bugUrl();
   }
   
+  /**
+   * Annotation for test classes that want to disable PointFields.
+   * PointFields will otherwise randomly used by some schemas.
+   */
+  @Documented
+  @Inherited
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE)
+  public @interface SuppressPointFields {
+    /** Point to JIRA entry. */
+    public String bugUrl() default "None";
+  }
+  
   // these are meant to be accessed sequentially, but are volatile just to ensure any test
   // thread will read the latest value
   protected static volatile SSLTestConfig sslConfig;
@@ -419,10 +439,12 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
     lrf = h.getRequestFactory("standard", 0, 20, CommonParams.VERSION, "2.2");
   }
   
-  /** sets system properties based on 
+  /** 
+   * Sets system properties to allow generation of random configurations of
+   * solrconfig.xml and schema.xml. 
+   * Sets properties used on  
    * {@link #newIndexWriterConfig(org.apache.lucene.analysis.Analyzer)}
-   * 
-   * configs can use these system properties to vary the indexwriter settings
+   *  and base schema.xml (Point Fields)
    */
   public static void newRandomConfig() {
     IndexWriterConfig iwc = newIndexWriterConfig(new MockAnalyzer(random()));
@@ -438,6 +460,20 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
       mergeSchedulerClass = "org.apache.lucene.index.ConcurrentMergeScheduler";
     }
     System.setProperty("solr.tests.mergeScheduler", mergeSchedulerClass);
+    if (RandomizedContext.current().getTargetClass().isAnnotationPresent(SuppressPointFields.class)
+        || (!PREFER_POINT_FIELDS && random().nextBoolean())) {
+      log.info("Using TrieFields");
+      System.setProperty("solr.tests.intClass", "int");
+      System.setProperty("solr.tests.longClass", "long");
+      System.setProperty("solr.tests.doubleClass", "double");
+      System.setProperty("solr.tests.floatClass", "float");
+    } else {
+      log.info("Using PointFields");
+      System.setProperty("solr.tests.intClass", "pint");
+      System.setProperty("solr.tests.longClass", "plong");
+      System.setProperty("solr.tests.doubleClass", "pdouble");
+      System.setProperty("solr.tests.floatClass", "pfloat");
+    }
   }
 
   public static Throwable getWrappedException(Throwable e) {


[13/38] lucene-solr:jira/solr-9857: LUCENE-7619: add WordDelimiterGraphFilter (replacing WordDelimiterFilter) to produce a correct token stream graph when splitting words

Posted by ab...@apache.org.
LUCENE-7619: add WordDelimiterGraphFilter (replacing WordDelimiterFilter) to produce a correct token stream graph when splitting words


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

Branch: refs/heads/jira/solr-9857
Commit: 637915b890d9f0e5cfaa6887609f221029327a25
Parents: 7d7e5d2
Author: Mike McCandless <mi...@apache.org>
Authored: Tue Jan 17 10:38:07 2017 -0500
Committer: Mike McCandless <mi...@apache.org>
Committed: Tue Jan 17 10:38:07 2017 -0500

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   5 +
 .../analysis/core/FlattenGraphFilter.java       | 418 +++++++++
 .../core/FlattenGraphFilterFactory.java         |  44 +
 .../miscellaneous/WordDelimiterFilter.java      |   9 +-
 .../WordDelimiterFilterFactory.java             |   6 +
 .../miscellaneous/WordDelimiterGraphFilter.java | 692 ++++++++++++++
 .../WordDelimiterGraphFilterFactory.java        | 199 ++++
 .../miscellaneous/WordDelimiterIterator.java    |  59 +-
 .../analysis/synonym/FlattenGraphFilter.java    | 417 ---------
 .../synonym/FlattenGraphFilterFactory.java      |  44 -
 .../lucene/analysis/synonym/SynonymFilter.java  |   1 +
 .../analysis/synonym/SynonymFilterFactory.java  |   1 +
 .../analysis/synonym/SynonymGraphFilter.java    |  11 +-
 ...ache.lucene.analysis.util.TokenFilterFactory |   3 +-
 .../analysis/core/TestFlattenGraphFilter.java   | 284 ++++++
 .../miscellaneous/TestWordDelimiterFilter.java  |  69 ++
 .../TestWordDelimiterGraphFilter.java           | 897 +++++++++++++++++++
 .../synonym/TestFlattenGraphFilter.java         | 284 ------
 .../synonym/TestSynonymGraphFilter.java         |  51 +-
 .../lucene/analysis/TokenStreamToAutomaton.java |  39 +-
 .../tokenattributes/OffsetAttributeImpl.java    |   2 +-
 .../PackedTokenAttributeImpl.java               |   2 +-
 .../PositionIncrementAttributeImpl.java         |   3 +-
 .../PositionLengthAttributeImpl.java            |   3 +-
 .../lucene/analysis/TestGraphTokenizers.java    |  53 +-
 .../suggest/analyzing/AnalyzingSuggester.java   |   3 +-
 .../analysis/BaseTokenStreamTestCase.java       | 114 ++-
 .../lucene/analysis/TokenStreamToDot.java       |   5 +-
 28 files changed, 2899 insertions(+), 819 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 2e015a3..4df7a67 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -76,6 +76,11 @@ New Features
 * LUCENE-7623: Add FunctionScoreQuery and FunctionMatchQuery (Alan Woodward,
   Adrien Grand, David Smiley)
 
+* LUCENE-7619: Add WordDelimiterGraphFilter, just like
+  WordDelimiterFilter except it produces correct token graphs so that
+  proximity queries at search time will produce correct results (Mike
+  McCandless)
+
 Bug Fixes
 
 * LUCENE-7630: Fix (Edge)NGramTokenFilter to no longer drop payloads

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilter.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilter.java
new file mode 100644
index 0000000..01e1f6f
--- /dev/null
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilter.java
@@ -0,0 +1,418 @@
+/*
+ * 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.lucene.analysis.core;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.analysis.TokenFilter;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.synonym.SynonymGraphFilter;
+import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
+import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
+import org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute;
+import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.util.RollingBuffer;
+
+/**
+ * Converts an incoming graph token stream, such as one from
+ * {@link SynonymGraphFilter}, into a flat form so that
+ * all nodes form a single linear chain with no side paths.  Every
+ * path through the graph touches every node.  This is necessary
+ * when indexing a graph token stream, because the index does not
+ * save {@link PositionLengthAttribute} and so it cannot
+ * preserve the graph structure.  However, at search time,
+ * query parsers can correctly handle the graph and this token
+ * filter should <b>not</b> be used.
+ *
+ * <p>If the graph was not already flat to start, this
+ * is likely a lossy process, i.e. it will often cause the 
+ * graph to accept token sequences it should not, and to
+ * reject token sequences it should not.
+ *
+ * <p>However, when applying synonyms during indexing, this
+ * is necessary because Lucene already does not index a graph 
+ * and so the indexing process is already lossy
+ * (it ignores the {@link PositionLengthAttribute}).
+ *
+ * @lucene.experimental
+ */
+public final class FlattenGraphFilter extends TokenFilter {
+
+  /** Holds all tokens leaving a given input position. */
+  private final static class InputNode implements RollingBuffer.Resettable {
+    private final List<AttributeSource.State> tokens = new ArrayList<>();
+
+    /** Our input node, or -1 if we haven't been assigned yet */
+    int node = -1;
+
+    /** Maximum to input node for all tokens leaving here; we use this
+     *  to know when we can freeze. */
+    int maxToNode = -1;
+
+    /** Where we currently map to; this changes (can only
+     *  increase as we see more input tokens), until we are finished
+     *  with this position. */
+    int outputNode = -1;
+
+    /** Which token (index into {@link #tokens}) we will next output. */
+    int nextOut;
+
+    @Override
+    public void reset() {
+      tokens.clear();
+      node = -1;
+      outputNode = -1;
+      maxToNode = -1;
+      nextOut = 0;
+    }
+  }
+
+  /** Gathers up merged input positions into a single output position,
+   *  only for the current "frontier" of nodes we've seen but can't yet
+   *  output because they are not frozen. */
+  private final static class OutputNode implements RollingBuffer.Resettable {
+    private final List<Integer> inputNodes = new ArrayList<>();
+
+    /** Node ID for this output, or -1 if we haven't been assigned yet. */
+    int node = -1;
+
+    /** Which input node (index into {@link #inputNodes}) we will next output. */
+    int nextOut;
+    
+    /** Start offset of tokens leaving this node. */
+    int startOffset = -1;
+
+    /** End offset of tokens arriving to this node. */
+    int endOffset = -1;
+
+    @Override
+    public void reset() {
+      inputNodes.clear();
+      node = -1;
+      nextOut = 0;
+      startOffset = -1;
+      endOffset = -1;
+    }
+  }
+
+  private final RollingBuffer<InputNode> inputNodes = new RollingBuffer<InputNode>() {
+    @Override
+    protected InputNode newInstance() {
+      return new InputNode();
+    }
+  };
+
+  private final RollingBuffer<OutputNode> outputNodes = new RollingBuffer<OutputNode>() {
+    @Override
+    protected OutputNode newInstance() {
+      return new OutputNode();
+    }
+  };
+
+  private final PositionIncrementAttribute posIncAtt = addAttribute(PositionIncrementAttribute.class);
+  private final PositionLengthAttribute posLenAtt = addAttribute(PositionLengthAttribute.class);
+  private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);
+
+  /** Which input node the last seen token leaves from */
+  private int inputFrom;
+
+  /** We are currently releasing tokens leaving from this output node */
+  private int outputFrom;
+
+  // for debugging:
+  //private int retOutputFrom;
+
+  private boolean done;
+
+  private int lastOutputFrom;
+
+  private int finalOffset;
+
+  private int finalPosInc;
+
+  private int maxLookaheadUsed;
+
+  private int lastStartOffset;
+
+  public FlattenGraphFilter(TokenStream in) {
+    super(in);
+  }
+
+  private boolean releaseBufferedToken() {
+
+    // We only need the while loop (retry) if we have a hole (an output node that has no tokens leaving):
+    while (outputFrom < outputNodes.getMaxPos()) {
+      OutputNode output = outputNodes.get(outputFrom);
+      if (output.inputNodes.isEmpty()) {
+        // No tokens arrived to this node, which happens for the first node
+        // after a hole:
+        //System.out.println("    skip empty outputFrom=" + outputFrom);
+        outputFrom++;
+        continue;
+      }
+
+      int maxToNode = -1;
+      for(int inputNodeID : output.inputNodes) {
+        InputNode inputNode = inputNodes.get(inputNodeID);
+        assert inputNode.outputNode == outputFrom;
+        maxToNode = Math.max(maxToNode, inputNode.maxToNode);
+      }
+      //System.out.println("  release maxToNode=" + maxToNode + " vs inputFrom=" + inputFrom);
+
+      // TODO: we could shrink the frontier here somewhat if we
+      // always output posLen=1 as part of our "sausagizing":
+      if (maxToNode <= inputFrom || done) {
+        //System.out.println("  output node merged these inputs: " + output.inputNodes);
+        // These tokens are now frozen
+        assert output.nextOut < output.inputNodes.size(): "output.nextOut=" + output.nextOut + " vs output.inputNodes.size()=" + output.inputNodes.size();
+        InputNode inputNode = inputNodes.get(output.inputNodes.get(output.nextOut));
+        if (done && inputNode.tokens.size() == 0 && outputFrom >= outputNodes.getMaxPos()) {
+          return false;
+        }
+        if (inputNode.tokens.size() == 0) {
+          assert inputNode.nextOut == 0;
+          assert output.nextOut == 0;
+          // Hole dest nodes should never be merged since 1) we always
+          // assign them to a new output position, and 2) since they never
+          // have arriving tokens they cannot be pushed:
+          assert output.inputNodes.size() == 1: output.inputNodes.size();
+          outputFrom++;
+          inputNodes.freeBefore(output.inputNodes.get(0));
+          outputNodes.freeBefore(outputFrom);
+          continue;
+        }
+
+        assert inputNode.nextOut < inputNode.tokens.size();
+
+        restoreState(inputNode.tokens.get(inputNode.nextOut));
+
+        // Correct posInc
+        assert outputFrom >= lastOutputFrom;
+        posIncAtt.setPositionIncrement(outputFrom - lastOutputFrom);
+        int toInputNodeID = inputNode.node + posLenAtt.getPositionLength();
+        InputNode toInputNode = inputNodes.get(toInputNodeID);
+
+        // Correct posLen
+        assert toInputNode.outputNode > outputFrom;
+        posLenAtt.setPositionLength(toInputNode.outputNode - outputFrom);
+        lastOutputFrom = outputFrom;
+        inputNode.nextOut++;
+        //System.out.println("  ret " + this);
+
+        OutputNode outputEndNode = outputNodes.get(toInputNode.outputNode);
+
+        // Correct offsets
+
+        // This is a bit messy; we must do this so offset don't go backwards,
+        // which would otherwise happen if the replacement has more tokens
+        // than the input:
+        int startOffset = Math.max(lastStartOffset, output.startOffset);
+
+        // We must do this in case the incoming tokens have broken offsets:
+        int endOffset = Math.max(startOffset, outputEndNode.endOffset);
+        
+        offsetAtt.setOffset(startOffset, endOffset);
+        lastStartOffset = startOffset;
+
+        if (inputNode.nextOut == inputNode.tokens.size()) {
+          output.nextOut++;
+          if (output.nextOut == output.inputNodes.size()) {
+            outputFrom++;
+            inputNodes.freeBefore(output.inputNodes.get(0));
+            outputNodes.freeBefore(outputFrom);
+          }
+        }
+
+        return true;
+      } else {
+        return false;
+      }
+    }
+
+    //System.out.println("    break false");
+    return false;
+  }
+
+  @Override
+  public boolean incrementToken() throws IOException {
+    //System.out.println("\nF.increment inputFrom=" + inputFrom + " outputFrom=" + outputFrom);
+
+    while (true) {
+      if (releaseBufferedToken()) {
+        //retOutputFrom += posIncAtt.getPositionIncrement();
+        //System.out.println("    return buffered: " + termAtt + " " + retOutputFrom + "-" + (retOutputFrom + posLenAtt.getPositionLength()));
+        //printStates();
+        return true;
+      } else if (done) {
+        //System.out.println("    done, return false");
+        return false;
+      }
+
+      if (input.incrementToken()) {
+        // Input node this token leaves from:
+        inputFrom += posIncAtt.getPositionIncrement();
+
+        int startOffset = offsetAtt.startOffset();
+        int endOffset = offsetAtt.endOffset();
+
+        // Input node this token goes to:
+        int inputTo = inputFrom + posLenAtt.getPositionLength();
+        //System.out.println("  input.inc " + termAtt + ": " + inputFrom + "-" + inputTo);
+
+        InputNode src = inputNodes.get(inputFrom);
+        if (src.node == -1) {
+          // This means the "from" node of this token was never seen as a "to" node,
+          // which should only happen if we just crossed a hole.  This is a challenging
+          // case for us because we normally rely on the full dependencies expressed
+          // by the arcs to assign outgoing node IDs.  It would be better if tokens
+          // were never dropped but instead just marked deleted with a new
+          // TermDeletedAttribute (boolean valued) ... but until that future, we have
+          // a hack here to forcefully jump the output node ID:
+          assert src.outputNode == -1;
+          src.node = inputFrom;
+
+          src.outputNode = outputNodes.getMaxPos() + 1;
+          //System.out.println("    hole: force to outputNode=" + src.outputNode);
+          OutputNode outSrc = outputNodes.get(src.outputNode);
+
+          // Not assigned yet:
+          assert outSrc.node == -1;
+          outSrc.node = src.outputNode;
+          outSrc.inputNodes.add(inputFrom);
+          outSrc.startOffset = startOffset;
+        } else {
+          OutputNode outSrc = outputNodes.get(src.outputNode);
+          if (outSrc.startOffset == -1 || startOffset > outSrc.startOffset) {
+            // "shrink wrap" the offsets so the original tokens (with most
+            // restrictive offsets) win:
+            outSrc.startOffset = Math.max(startOffset, outSrc.startOffset);
+          }
+        }
+
+        // Buffer this token:
+        src.tokens.add(captureState());
+        src.maxToNode = Math.max(src.maxToNode, inputTo);
+        maxLookaheadUsed = Math.max(maxLookaheadUsed, inputNodes.getBufferSize());
+
+        InputNode dest = inputNodes.get(inputTo);
+        if (dest.node == -1) {
+          // Common case: first time a token is arriving to this input position:
+          dest.node = inputTo;
+        }
+
+        // Always number output nodes sequentially:
+        int outputEndNode = src.outputNode + 1;
+
+        if (outputEndNode > dest.outputNode) {
+          if (dest.outputNode != -1) {
+            boolean removed = outputNodes.get(dest.outputNode).inputNodes.remove(Integer.valueOf(inputTo));
+            assert removed;
+          }
+          //System.out.println("    increase output node: " + dest.outputNode + " vs " + outputEndNode);
+          outputNodes.get(outputEndNode).inputNodes.add(inputTo);
+          dest.outputNode = outputEndNode;
+
+          // Since all we ever do is merge incoming nodes together, and then renumber
+          // the merged nodes sequentially, we should only ever assign smaller node
+          // numbers:
+          assert outputEndNode <= inputTo: "outputEndNode=" + outputEndNode + " vs inputTo=" + inputTo;
+        }
+
+        OutputNode outDest = outputNodes.get(dest.outputNode);
+        // "shrink wrap" the offsets so the original tokens (with most
+        // restrictive offsets) win:
+        if (outDest.endOffset == -1 || endOffset < outDest.endOffset) {
+          outDest.endOffset = endOffset;
+        }
+
+      } else {
+        //System.out.println("  got false from input");
+        input.end();
+        finalPosInc = posIncAtt.getPositionIncrement();
+        finalOffset = offsetAtt.endOffset();
+        done = true;
+        // Don't return false here: we need to force release any buffered tokens now
+      }
+    }
+  }
+
+  // Only for debugging:
+  /*
+  private void printStates() {
+    System.out.println("states:");
+    for(int i=outputFrom;i<outputNodes.getMaxPos();i++) {
+      OutputNode outputNode = outputNodes.get(i);
+      System.out.println("  output " + i + ": inputs " + outputNode.inputNodes);
+      for(int inputNodeID : outputNode.inputNodes) {
+        InputNode inputNode = inputNodes.get(inputNodeID);
+        assert inputNode.outputNode == i;
+      }
+    }
+  }
+  */
+
+  @Override
+  public void end() throws IOException {
+    if (done == false) {
+      super.end();
+    } else {
+      // NOTE, shady: don't call super.end, because we did already from incrementToken
+    }
+
+    clearAttributes();
+    if (done) {
+      // On exc, done is false, and we will not have set these:
+      posIncAtt.setPositionIncrement(finalPosInc);
+      offsetAtt.setOffset(finalOffset, finalOffset);
+    } else {
+      super.end();
+    }
+  }
+  
+  @Override
+  public void reset() throws IOException {
+    //System.out.println("F: reset");
+    super.reset();
+    inputFrom = -1;
+    inputNodes.reset();
+    InputNode in = inputNodes.get(0);
+    in.node = 0;
+    in.outputNode = 0;
+
+    outputNodes.reset();
+    OutputNode out = outputNodes.get(0);
+    out.node = 0;
+    out.inputNodes.add(0);
+    out.startOffset = 0;
+    outputFrom = 0;
+    //retOutputFrom = -1;
+    lastOutputFrom = -1;
+    done = false;
+    finalPosInc = -1;
+    finalOffset = -1;
+    lastStartOffset = 0;
+    maxLookaheadUsed = 0;
+  }
+
+  /** For testing */
+  public int getMaxLookaheadUsed() {
+    return maxLookaheadUsed;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilterFactory.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilterFactory.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilterFactory.java
new file mode 100644
index 0000000..920ab3d
--- /dev/null
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilterFactory.java
@@ -0,0 +1,44 @@
+/*
+ * 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.lucene.analysis.core;
+
+import java.util.Map;
+
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.util.TokenFilterFactory;
+
+/** 
+ * Factory for {@link FlattenGraphFilter}. 
+ *
+ * @lucene.experimental
+ */
+public class FlattenGraphFilterFactory extends TokenFilterFactory {
+
+  /** Creates a new FlattenGraphFilterFactory */
+  public FlattenGraphFilterFactory(Map<String,String> args) {
+    super(args);
+    if (!args.isEmpty()) {
+      throw new IllegalArgumentException("Unknown parameters: " + args);
+    }
+  }
+  
+  @Override
+  public TokenStream create(TokenStream input) {
+    return new FlattenGraphFilter(input);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.java
index f80ed8a..aef697c 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.java
@@ -28,6 +28,7 @@ import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
+import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.AttributeSource;
 import org.apache.lucene.util.InPlaceMergeSorter;
@@ -80,7 +81,12 @@ import org.apache.lucene.util.InPlaceMergeSorter;
  * the current {@link StandardTokenizer} immediately removes many intra-word
  * delimiters, it is recommended that this filter be used after a tokenizer that
  * does not do this (such as {@link WhitespaceTokenizer}).
+ *
+ * @deprecated Use {@link WordDelimiterGraphFilter} instead: it produces a correct
+ * token graph so that e.g. {@link PhraseQuery} works correctly when it's used in
+ * the search time analyzer.
  */
+@Deprecated
 public final class WordDelimiterFilter extends TokenFilter {
   
   public static final int LOWER = 0x01;
@@ -116,7 +122,7 @@ public final class WordDelimiterFilter extends TokenFilter {
   /**
    * Causes maximum runs of word parts to be catenated:
    * <p>
-   * "wi-fi" =&gt; "wifi"
+   * "500-42" =&gt; "50042"
    */
   public static final int CATENATE_NUMBERS = 8;
 
@@ -494,7 +500,6 @@ public final class WordDelimiterFilter extends TokenFilter {
   private void generatePart(boolean isSingleWord) {
     clearAttributes();
     termAttribute.copyBuffer(savedBuffer, iterator.current, iterator.end - iterator.current);
-
     int startOffset = savedStartOffset + iterator.current;
     int endOffset = savedStartOffset + iterator.end;
     

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilterFactory.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilterFactory.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilterFactory.java
index 6a15b55..0002d65 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilterFactory.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilterFactory.java
@@ -31,6 +31,7 @@ import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.analysis.util.ResourceLoader;
 import org.apache.lucene.analysis.util.ResourceLoaderAware;
 import org.apache.lucene.analysis.util.TokenFilterFactory;
+import org.apache.lucene.search.PhraseQuery;
 
 import static org.apache.lucene.analysis.miscellaneous.WordDelimiterFilter.*;
 
@@ -47,7 +48,12 @@ import static org.apache.lucene.analysis.miscellaneous.WordDelimiterFilter.*;
  *             types="wdfftypes.txt" /&gt;
  *   &lt;/analyzer&gt;
  * &lt;/fieldType&gt;</pre>
+ *
+ * @deprecated Use {@link WordDelimiterGraphFilterFactory} instead: it produces a correct
+ * token graph so that e.g. {@link PhraseQuery} works correctly when it's used in
+ * the search time analyzer.
  */
+@Deprecated
 public class WordDelimiterFilterFactory extends TokenFilterFactory implements ResourceLoaderAware {
   public static final String PROTECTED_TOKENS = "protected";
   public static final String TYPES = "types";

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java
new file mode 100644
index 0000000..ea6f6cd
--- /dev/null
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java
@@ -0,0 +1,692 @@
+/*
+ * 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.lucene.analysis.miscellaneous;
+
+import java.io.IOException;
+
+import org.apache.lucene.analysis.CharArraySet;
+import org.apache.lucene.analysis.TokenFilter;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.core.WhitespaceTokenizer;
+import org.apache.lucene.analysis.standard.StandardTokenizer;
+import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
+import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
+import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
+import org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute;
+import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
+import org.apache.lucene.search.PhraseQuery;
+import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.util.InPlaceMergeSorter;
+import org.apache.lucene.util.RamUsageEstimator;
+
+/**
+ * Splits words into subwords and performs optional transformations on subword
+ * groups, producing a correct token graph so that e.g. {@link PhraseQuery} can
+ * work correctly when this filter is used in the search-time analyzer.  Unlike
+ * the deprecated {@link WordDelimiterFilter}, this token filter produces a
+ * correct token graph as output.  However, it cannot consume an input token
+ * graph correctly.
+ *
+ * <p>
+ * Words are split into subwords with the following rules:
+ * <ul>
+ * <li>split on intra-word delimiters (by default, all non alpha-numeric
+ * characters): <code>"Wi-Fi"</code> &#8594; <code>"Wi", "Fi"</code></li>
+ * <li>split on case transitions: <code>"PowerShot"</code> &#8594;
+ * <code>"Power", "Shot"</code></li>
+ * <li>split on letter-number transitions: <code>"SD500"</code> &#8594;
+ * <code>"SD", "500"</code></li>
+ * <li>leading and trailing intra-word delimiters on each subword are ignored:
+ * <code>"//hello---there, 'dude'"</code> &#8594;
+ * <code>"hello", "there", "dude"</code></li>
+ * <li>trailing "'s" are removed for each subword: <code>"O'Neil's"</code>
+ * &#8594; <code>"O", "Neil"</code>
+ * <ul>
+ * <li>Note: this step isn't performed in a separate filter because of possible
+ * subword combinations.</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * 
+ * The <b>combinations</b> parameter affects how subwords are combined:
+ * <ul>
+ * <li>combinations="0" causes no subword combinations: <code>"PowerShot"</code>
+ * &#8594; <code>0:"Power", 1:"Shot"</code> (0 and 1 are the token positions)</li>
+ * <li>combinations="1" means that in addition to the subwords, maximum runs of
+ * non-numeric subwords are catenated and produced at the same position of the
+ * last subword in the run:
+ * <ul>
+ * <li><code>"PowerShot"</code> &#8594;
+ * <code>0:"Power", 1:"Shot" 1:"PowerShot"</code></li>
+ * <li><code>"A's+B's&amp;C's"</code> &gt; <code>0:"A", 1:"B", 2:"C", 2:"ABC"</code>
+ * </li>
+ * <li><code>"Super-Duper-XL500-42-AutoCoder!"</code> &#8594;
+ * <code>0:"Super", 1:"Duper", 2:"XL", 2:"SuperDuperXL", 3:"500" 4:"42", 5:"Auto", 6:"Coder", 6:"AutoCoder"</code>
+ * </li>
+ * </ul>
+ * </li>
+ * </ul>
+ * One use for {@link WordDelimiterGraphFilter} is to help match words with different
+ * subword delimiters. For example, if the source text contained "wi-fi" one may
+ * want "wifi" "WiFi" "wi-fi" "wi+fi" queries to all match. One way of doing so
+ * is to specify combinations="1" in the analyzer used for indexing, and
+ * combinations="0" (the default) in the analyzer used for querying. Given that
+ * the current {@link StandardTokenizer} immediately removes many intra-word
+ * delimiters, it is recommended that this filter be used after a tokenizer that
+ * does not do this (such as {@link WhitespaceTokenizer}).
+ */
+
+public final class WordDelimiterGraphFilter extends TokenFilter {
+  
+  /**
+   * Causes parts of words to be generated:
+   * <p>
+   * "PowerShot" =&gt; "Power" "Shot"
+   */
+  public static final int GENERATE_WORD_PARTS = 1;
+
+  /**
+   * Causes number subwords to be generated:
+   * <p>
+   * "500-42" =&gt; "500" "42"
+   */
+  public static final int GENERATE_NUMBER_PARTS = 2;
+
+  /**
+   * Causes maximum runs of word parts to be catenated:
+   * <p>
+   * "wi-fi" =&gt; "wifi"
+   */
+  public static final int CATENATE_WORDS = 4;
+
+  /**
+   * Causes maximum runs of number parts to be catenated:
+   * <p>
+   * "500-42" =&gt; "50042"
+   */
+  public static final int CATENATE_NUMBERS = 8;
+
+  /**
+   * Causes all subword parts to be catenated:
+   * <p>
+   * "wi-fi-4000" =&gt; "wifi4000"
+   */
+  public static final int CATENATE_ALL = 16;
+
+  /**
+   * Causes original words are preserved and added to the subword list (Defaults to false)
+   * <p>
+   * "500-42" =&gt; "500" "42" "500-42"
+   */
+  public static final int PRESERVE_ORIGINAL = 32;
+
+  /**
+   * Causes lowercase -&gt; uppercase transition to start a new subword.
+   */
+  public static final int SPLIT_ON_CASE_CHANGE = 64;
+
+  /**
+   * If not set, causes numeric changes to be ignored (subwords will only be generated
+   * given SUBWORD_DELIM tokens).
+   */
+  public static final int SPLIT_ON_NUMERICS = 128;
+
+  /**
+   * Causes trailing "'s" to be removed for each subword
+   * <p>
+   * "O'Neil's" =&gt; "O", "Neil"
+   */
+  public static final int STEM_ENGLISH_POSSESSIVE = 256;
+  
+  /**
+   * If not null is the set of tokens to protect from being delimited
+   *
+   */
+  final CharArraySet protWords;
+
+  private final int flags;
+
+  // packs start pos, end pos, start part, end part (= slice of the term text) for each buffered part:
+  private int[] bufferedParts = new int[16];
+  private int bufferedLen;
+  private int bufferedPos;
+
+  // holds text for each buffered part, or null if it's a simple slice of the original term
+  private char[][] bufferedTermParts = new char[4][];
+  
+  private final CharTermAttribute termAttribute = addAttribute(CharTermAttribute.class);
+  private final OffsetAttribute offsetAttribute = addAttribute(OffsetAttribute.class);
+  private final PositionIncrementAttribute posIncAttribute = addAttribute(PositionIncrementAttribute.class);
+  private final PositionLengthAttribute posLenAttribute = addAttribute(PositionLengthAttribute.class);
+  private final TypeAttribute typeAttribute = addAttribute(TypeAttribute.class);
+
+  // used for iterating word delimiter breaks
+  private final WordDelimiterIterator iterator;
+
+  // used for concatenating runs of similar typed subwords (word,number)
+  private final WordDelimiterConcatenation concat = new WordDelimiterConcatenation();
+
+  // number of subwords last output by concat.
+  private int lastConcatCount;
+
+  // used for catenate all
+  private final WordDelimiterConcatenation concatAll = new WordDelimiterConcatenation();
+
+  // used for accumulating position increment gaps so that we preserve incoming holes:
+  private int accumPosInc;
+
+  private char[] savedTermBuffer = new char[16];
+  private int savedTermLength;
+  private int savedStartOffset;
+  private int savedEndOffset;
+  private AttributeSource.State savedState;
+  
+  // if length by start + end offsets doesn't match the term text then assume
+  // this is a synonym and don't adjust the offsets.
+  private boolean hasIllegalOffsets;
+
+  private int wordPos;
+
+  /**
+   * Creates a new WordDelimiterGraphFilter
+   *
+   * @param in TokenStream to be filtered
+   * @param charTypeTable table containing character types
+   * @param configurationFlags Flags configuring the filter
+   * @param protWords If not null is the set of tokens to protect from being delimited
+   */
+  public WordDelimiterGraphFilter(TokenStream in, byte[] charTypeTable, int configurationFlags, CharArraySet protWords) {
+    super(in);
+    if ((configurationFlags &
+        ~(GENERATE_WORD_PARTS |
+          GENERATE_NUMBER_PARTS |
+          CATENATE_WORDS |
+          CATENATE_NUMBERS |
+          CATENATE_ALL |
+          PRESERVE_ORIGINAL |
+          SPLIT_ON_CASE_CHANGE |
+          SPLIT_ON_NUMERICS |
+          STEM_ENGLISH_POSSESSIVE)) != 0) {
+      throw new IllegalArgumentException("flags contains unrecognized flag: " + configurationFlags);
+    }
+    this.flags = configurationFlags;
+    this.protWords = protWords;
+    this.iterator = new WordDelimiterIterator(
+        charTypeTable, has(SPLIT_ON_CASE_CHANGE), has(SPLIT_ON_NUMERICS), has(STEM_ENGLISH_POSSESSIVE));
+  }
+
+  /**
+   * Creates a new WordDelimiterGraphFilter using {@link WordDelimiterIterator#DEFAULT_WORD_DELIM_TABLE}
+   * as its charTypeTable
+   *
+   * @param in TokenStream to be filtered
+   * @param configurationFlags Flags configuring the filter
+   * @param protWords If not null is the set of tokens to protect from being delimited
+   */
+  public WordDelimiterGraphFilter(TokenStream in, int configurationFlags, CharArraySet protWords) {
+    this(in, WordDelimiterIterator.DEFAULT_WORD_DELIM_TABLE, configurationFlags, protWords);
+  }
+
+  /** Iterates all words parts and concatenations, buffering up the term parts we should return. */
+  private void bufferWordParts() throws IOException {
+
+    saveState();
+
+    // if length by start + end offsets doesn't match the term's text then set offsets for all our word parts/concats to the incoming
+    // offsets.  this can happen if WDGF is applied to an injected synonym, or to a stem'd form, etc:
+    hasIllegalOffsets = (savedEndOffset - savedStartOffset != savedTermLength);
+
+    bufferedLen = 0;
+    lastConcatCount = 0;
+    wordPos = 0;
+
+    if (iterator.isSingleWord()) {
+      buffer(wordPos, wordPos+1, iterator.current, iterator.end);
+      wordPos++;
+      iterator.next();
+    } else {
+
+      // iterate all words parts, possibly buffering them, building up concatenations and possibly buffering them too:
+      while (iterator.end != WordDelimiterIterator.DONE) {
+        int wordType = iterator.type();
+      
+        // do we already have queued up incompatible concatenations?
+        if (concat.isNotEmpty() && (concat.type & wordType) == 0) {
+          flushConcatenation(concat);
+        }
+
+        // add subwords depending upon options
+        if (shouldConcatenate(wordType)) {
+          concatenate(concat);
+        }
+      
+        // add all subwords (catenateAll)
+        if (has(CATENATE_ALL)) {
+          concatenate(concatAll);
+        }
+      
+        // if we should output the word or number part
+        if (shouldGenerateParts(wordType)) {
+          buffer(wordPos, wordPos+1, iterator.current, iterator.end);
+          wordPos++;
+        }
+        iterator.next();
+      }
+
+      if (concat.isNotEmpty()) {
+        // flush final concatenation
+        flushConcatenation(concat);
+      }
+        
+      if (concatAll.isNotEmpty()) {
+        // only if we haven't output this same combo above, e.g. PowerShot with CATENATE_WORDS:
+        if (concatAll.subwordCount > lastConcatCount) {
+          if (wordPos == concatAll.startPos) {
+            // we are not generating parts, so we must advance wordPos now
+            wordPos++;
+          }
+          concatAll.write();
+        }
+        concatAll.clear();
+      }
+    }
+
+    if (has(PRESERVE_ORIGINAL)) {
+      if (wordPos == 0) {
+        // can happen w/ strange flag combos and inputs :)
+        wordPos++;
+      }
+      // add the original token now so that we can set the correct end position
+      buffer(0, wordPos, 0, savedTermLength);
+    }
+            
+    sorter.sort(0, bufferedLen);
+    wordPos = 0;
+
+    // set back to 0 for iterating from the buffer
+    bufferedPos = 0;
+  }
+
+  @Override
+  public boolean incrementToken() throws IOException {
+    while (true) {
+      if (savedState == null) {
+
+        // process a new input token
+        if (input.incrementToken() == false) {
+          return false;
+        }
+
+        int termLength = termAttribute.length();
+        char[] termBuffer = termAttribute.buffer();
+
+        accumPosInc += posIncAttribute.getPositionIncrement();
+
+        // iterate & cache all word parts up front:
+        iterator.setText(termBuffer, termLength);
+        iterator.next();
+        
+        // word of no delimiters, or protected word: just return it
+        if ((iterator.current == 0 && iterator.end == termLength) ||
+            (protWords != null && protWords.contains(termBuffer, 0, termLength))) {
+          posIncAttribute.setPositionIncrement(accumPosInc);
+          accumPosInc = 0;
+          return true;
+        }
+        
+        // word of simply delimiters: swallow this token, creating a hole, and move on to next token
+        if (iterator.end == WordDelimiterIterator.DONE) {
+          if (has(PRESERVE_ORIGINAL) == false) {
+            continue;
+          } else {
+            return true;
+          }
+        }
+
+        // otherwise, we have delimiters, process & buffer all parts:
+        bufferWordParts();
+      }
+
+      if (bufferedPos < bufferedLen) {
+        clearAttributes();
+        restoreState(savedState);
+
+        char[] termPart = bufferedTermParts[bufferedPos];
+        int startPos = bufferedParts[4*bufferedPos];
+        int endPos = bufferedParts[4*bufferedPos+1];
+        int startPart = bufferedParts[4*bufferedPos+2];
+        int endPart = bufferedParts[4*bufferedPos+3];
+        bufferedPos++;
+
+        if (hasIllegalOffsets) {
+          offsetAttribute.setOffset(savedStartOffset, savedEndOffset);
+        } else {
+          offsetAttribute.setOffset(savedStartOffset + startPart, savedStartOffset + endPart);
+        }
+
+        if (termPart == null) {
+          termAttribute.copyBuffer(savedTermBuffer, startPart, endPart - startPart);
+        } else {
+          termAttribute.copyBuffer(termPart, 0, termPart.length);
+        }
+
+        posIncAttribute.setPositionIncrement(accumPosInc + startPos - wordPos);
+        accumPosInc = 0;
+        posLenAttribute.setPositionLength(endPos - startPos);
+        wordPos = startPos;
+        return true;
+      }
+        
+      // no saved concatenations, on to the next input word
+      savedState = null;
+    }
+  }
+
+  @Override
+  public void reset() throws IOException {
+    super.reset();
+    accumPosInc = 0;
+    savedState = null;
+    concat.clear();
+    concatAll.clear();
+  }
+
+  // ================================================= Helper Methods ================================================
+
+  private class PositionSorter extends InPlaceMergeSorter {
+    @Override
+    protected int compare(int i, int j) {
+      // sort by smaller start position
+      int iPosStart = bufferedParts[4*i];
+      int jPosStart = bufferedParts[4*j];
+      int cmp = Integer.compare(iPosStart, jPosStart);
+      if (cmp != 0) {
+        return cmp;
+      }
+
+      // tie break by longest pos length:
+      int iPosEnd = bufferedParts[4*i+1];
+      int jPosEnd = bufferedParts[4*j+1];
+      return Integer.compare(jPosEnd, iPosEnd);
+    }
+
+    @Override
+    protected void swap(int i, int j) {
+      int iOffset = 4*i;
+      int jOffset = 4*j;
+      for(int x=0;x<4;x++) {
+        int tmp = bufferedParts[iOffset+x];
+        bufferedParts[iOffset+x] = bufferedParts[jOffset+x];
+        bufferedParts[jOffset+x] = tmp;
+      }
+
+      char[] tmp2 = bufferedTermParts[i];
+      bufferedTermParts[i] = bufferedTermParts[j];
+      bufferedTermParts[j] = tmp2;
+    }
+  }
+  
+  final PositionSorter sorter = new PositionSorter();
+
+  /** 
+   * startPos, endPos -> graph start/end position
+   * startPart, endPart -> slice of the original term for this part
+   */
+
+  void buffer(int startPos, int endPos, int startPart, int endPart) {
+    buffer(null, startPos, endPos, startPart, endPart);
+  }
+
+  /** 
+   * a null termPart means it's a simple slice of the original term
+   */
+  void buffer(char[] termPart, int startPos, int endPos, int startPart, int endPart) {
+    /*
+    System.out.println("buffer: pos=" + startPos + "-" + endPos + " part=" + startPart + "-" + endPart);
+    if (termPart != null) {
+      System.out.println("  termIn=" + new String(termPart));
+    } else {
+      System.out.println("  term=" + new String(savedTermBuffer, startPart, endPart-startPart));
+    }
+    */
+    assert endPos > startPos: "startPos=" + startPos + " endPos=" + endPos;
+    assert endPart > startPart || (endPart == 0 && startPart == 0 && savedTermLength == 0): "startPart=" + startPart + " endPart=" + endPart;
+    if ((bufferedLen+1)*4 > bufferedParts.length) {
+      bufferedParts = ArrayUtil.grow(bufferedParts, (bufferedLen+1)*4);
+    }
+    if (bufferedTermParts.length == bufferedLen) {
+      int newSize = ArrayUtil.oversize(bufferedLen+1, RamUsageEstimator.NUM_BYTES_OBJECT_REF);
+      char[][] newArray = new char[newSize][];
+      System.arraycopy(bufferedTermParts, 0, newArray, 0, bufferedTermParts.length);
+      bufferedTermParts = newArray;
+    }
+    bufferedTermParts[bufferedLen] = termPart;
+    bufferedParts[bufferedLen*4] = startPos;
+    bufferedParts[bufferedLen*4+1] = endPos;
+    bufferedParts[bufferedLen*4+2] = startPart;
+    bufferedParts[bufferedLen*4+3] = endPart;
+    bufferedLen++;
+  }
+  
+  /**
+   * Saves the existing attribute states
+   */
+  private void saveState() {
+    savedTermLength = termAttribute.length();
+    savedStartOffset = offsetAttribute.startOffset();
+    savedEndOffset = offsetAttribute.endOffset();
+    savedState = captureState();
+
+    if (savedTermBuffer.length < savedTermLength) {
+      savedTermBuffer = new char[ArrayUtil.oversize(savedTermLength, Character.BYTES)];
+    }
+
+    System.arraycopy(termAttribute.buffer(), 0, savedTermBuffer, 0, savedTermLength);
+  }
+
+  /**
+   * Flushes the given WordDelimiterConcatenation by either writing its concat and then clearing, or just clearing.
+   *
+   * @param concat WordDelimiterConcatenation that will be flushed
+   */
+  private void flushConcatenation(WordDelimiterConcatenation concat) {
+    if (wordPos == concat.startPos) {
+      // we are not generating parts, so we must advance wordPos now
+      wordPos++;
+    }
+    lastConcatCount = concat.subwordCount;
+    if (concat.subwordCount != 1 || shouldGenerateParts(concat.type) == false) {
+      concat.write();
+    }
+    concat.clear();
+  }
+
+  /**
+   * Determines whether to concatenate a word or number if the current word is the given type
+   *
+   * @param wordType Type of the current word used to determine if it should be concatenated
+   * @return {@code true} if concatenation should occur, {@code false} otherwise
+   */
+  private boolean shouldConcatenate(int wordType) {
+    return (has(CATENATE_WORDS) && WordDelimiterIterator.isAlpha(wordType)) || (has(CATENATE_NUMBERS) && WordDelimiterIterator.isDigit(wordType));
+  }
+
+  /**
+   * Determines whether a word/number part should be generated for a word of the given type
+   *
+   * @param wordType Type of the word used to determine if a word/number part should be generated
+   * @return {@code true} if a word/number part should be generated, {@code false} otherwise
+   */
+  private boolean shouldGenerateParts(int wordType) {
+    return (has(GENERATE_WORD_PARTS) && WordDelimiterIterator.isAlpha(wordType)) || (has(GENERATE_NUMBER_PARTS) && WordDelimiterIterator.isDigit(wordType));
+  }
+
+  /**
+   * Concatenates the saved buffer to the given WordDelimiterConcatenation
+   *
+   * @param concatenation WordDelimiterConcatenation to concatenate the buffer to
+   */
+  private void concatenate(WordDelimiterConcatenation concatenation) {
+    if (concatenation.isEmpty()) {
+      concatenation.type = iterator.type();
+      concatenation.startPart = iterator.current;
+      concatenation.startPos = wordPos;
+    }
+    concatenation.append(savedTermBuffer, iterator.current, iterator.end - iterator.current);
+    concatenation.endPart = iterator.end;
+  }
+
+  /**
+   * Determines whether the given flag is set
+   *
+   * @param flag Flag to see if set
+   * @return {@code true} if flag is set
+   */
+  private boolean has(int flag) {
+    return (flags & flag) != 0;
+  }
+
+  // ================================================= Inner Classes =================================================
+
+  /**
+   * A WDF concatenated 'run'
+   */
+  final class WordDelimiterConcatenation {
+    final StringBuilder buffer = new StringBuilder();
+    int startPart;
+    int endPart;
+    int startPos;
+    int type;
+    int subwordCount;
+
+    /**
+     * Appends the given text of the given length, to the concetenation at the given offset
+     *
+     * @param text Text to append
+     * @param offset Offset in the concetenation to add the text
+     * @param length Length of the text to append
+     */
+    void append(char text[], int offset, int length) {
+      buffer.append(text, offset, length);
+      subwordCount++;
+    }
+
+    /**
+     * Writes the concatenation to part buffer
+     */
+    void write() {
+      char[] termPart = new char[buffer.length()];
+      buffer.getChars(0, buffer.length(), termPart, 0);
+      buffer(termPart, startPos, wordPos, startPart, endPart);
+    }
+
+    /**
+     * Determines if the concatenation is empty
+     *
+     * @return {@code true} if the concatenation is empty, {@code false} otherwise
+     */
+    boolean isEmpty() {
+      return buffer.length() == 0;
+    }
+
+    boolean isNotEmpty() {
+      return isEmpty() == false;
+    }
+
+    /**
+     * Clears the concatenation and resets its state
+     */
+    void clear() {
+      buffer.setLength(0);
+      startPart = endPart = type = subwordCount = 0;
+    }
+  }
+
+  /** Returns string representation of configuration flags */
+  public static String flagsToString(int flags) {
+    StringBuilder b = new StringBuilder();
+    if ((flags & GENERATE_WORD_PARTS) != 0) {
+      b.append("GENERATE_WORD_PARTS");
+    }
+    if ((flags & GENERATE_NUMBER_PARTS) != 0) {
+      if (b.length() > 0) {
+        b.append(" | ");
+      }
+      b.append("GENERATE_NUMBER_PARTS");
+    }
+    if ((flags & CATENATE_WORDS) != 0) {
+      if (b.length() > 0) {
+        b.append(" | ");
+      }
+      b.append("CATENATE_WORDS");
+    }
+    if ((flags & CATENATE_NUMBERS) != 0) {
+      if (b.length() > 0) {
+        b.append(" | ");
+      }
+      b.append("CATENATE_NUMBERS");
+    }
+    if ((flags & CATENATE_ALL) != 0) {
+      if (b.length() > 0) {
+        b.append(" | ");
+      }
+      b.append("CATENATE_ALL");
+    }
+    if ((flags & PRESERVE_ORIGINAL) != 0) {
+      if (b.length() > 0) {
+        b.append(" | ");
+      }
+      b.append("PRESERVE_ORIGINAL");
+    }
+    if ((flags & SPLIT_ON_CASE_CHANGE) != 0) {
+      if (b.length() > 0) {
+        b.append(" | ");
+      }
+      b.append("SPLIT_ON_CASE_CHANGE");
+    }
+    if ((flags & SPLIT_ON_NUMERICS) != 0) {
+      if (b.length() > 0) {
+        b.append(" | ");
+      }
+      b.append("SPLIT_ON_NUMERICS");
+    }
+    if ((flags & STEM_ENGLISH_POSSESSIVE) != 0) {
+      if (b.length() > 0) {
+        b.append(" | ");
+      }
+      b.append("STEM_ENGLISH_POSSESSIVE");
+    }
+
+    return b.toString();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append("WordDelimiterGraphFilter(flags=");
+    b.append(flagsToString(flags));
+    b.append(')');
+    return b.toString();
+  }
+  
+  // questions:
+  // negative numbers?  -42 indexed as just 42?
+  // dollar sign?  $42
+  // percent sign?  33%
+  // downsides:  if source text is "powershot" then a query of "PowerShot" won't match!
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilterFactory.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilterFactory.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilterFactory.java
new file mode 100644
index 0000000..a06cc75
--- /dev/null
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilterFactory.java
@@ -0,0 +1,199 @@
+/*
+ * 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.lucene.analysis.miscellaneous;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.lucene.analysis.CharArraySet;
+import org.apache.lucene.analysis.TokenFilter;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.util.ResourceLoader;
+import org.apache.lucene.analysis.util.ResourceLoaderAware;
+import org.apache.lucene.analysis.util.TokenFilterFactory;
+
+import static org.apache.lucene.analysis.miscellaneous.WordDelimiterGraphFilter.*;
+import static org.apache.lucene.analysis.miscellaneous.WordDelimiterIterator.*;
+
+/**
+ * Factory for {@link WordDelimiterGraphFilter}.
+ * <pre class="prettyprint">
+ * &lt;fieldType name="text_wd" class="solr.TextField" positionIncrementGap="100"&gt;
+ *   &lt;analyzer&gt;
+ *     &lt;tokenizer class="solr.WhitespaceTokenizerFactory"/&gt;
+ *     &lt;filter class="solr.WordDelimiterGraphFilterFactory" protected="protectedword.txt"
+ *             preserveOriginal="0" splitOnNumerics="1" splitOnCaseChange="1"
+ *             catenateWords="0" catenateNumbers="0" catenateAll="0"
+ *             generateWordParts="1" generateNumberParts="1" stemEnglishPossessive="1"
+ *             types="wdfftypes.txt" /&gt;
+ *   &lt;/analyzer&gt;
+ * &lt;/fieldType&gt;</pre>
+ */
+public class WordDelimiterGraphFilterFactory extends TokenFilterFactory implements ResourceLoaderAware {
+  public static final String PROTECTED_TOKENS = "protected";
+  public static final String TYPES = "types";
+
+  private final String wordFiles;
+  private final String types;
+  private final int flags;
+  byte[] typeTable = null;
+  private CharArraySet protectedWords = null;
+  
+  /** Creates a new WordDelimiterGraphFilterFactory */
+  public WordDelimiterGraphFilterFactory(Map<String, String> args) {
+    super(args);
+    int flags = 0;
+    if (getInt(args, "generateWordParts", 1) != 0) {
+      flags |= GENERATE_WORD_PARTS;
+    }
+    if (getInt(args, "generateNumberParts", 1) != 0) {
+      flags |= GENERATE_NUMBER_PARTS;
+    }
+    if (getInt(args, "catenateWords", 0) != 0) {
+      flags |= CATENATE_WORDS;
+    }
+    if (getInt(args, "catenateNumbers", 0) != 0) {
+      flags |= CATENATE_NUMBERS;
+    }
+    if (getInt(args, "catenateAll", 0) != 0) {
+      flags |= CATENATE_ALL;
+    }
+    if (getInt(args, "splitOnCaseChange", 1) != 0) {
+      flags |= SPLIT_ON_CASE_CHANGE;
+    }
+    if (getInt(args, "splitOnNumerics", 1) != 0) {
+      flags |= SPLIT_ON_NUMERICS;
+    }
+    if (getInt(args, "preserveOriginal", 0) != 0) {
+      flags |= PRESERVE_ORIGINAL;
+    }
+    if (getInt(args, "stemEnglishPossessive", 1) != 0) {
+      flags |= STEM_ENGLISH_POSSESSIVE;
+    }
+    wordFiles = get(args, PROTECTED_TOKENS);
+    types = get(args, TYPES);
+    this.flags = flags;
+    if (!args.isEmpty()) {
+      throw new IllegalArgumentException("Unknown parameters: " + args);
+    }
+  }
+  
+  @Override
+  public void inform(ResourceLoader loader) throws IOException {
+    if (wordFiles != null) {  
+      protectedWords = getWordSet(loader, wordFiles, false);
+    }
+    if (types != null) {
+      List<String> files = splitFileNames( types );
+      List<String> wlist = new ArrayList<>();
+      for( String file : files ){
+        List<String> lines = getLines(loader, file.trim());
+        wlist.addAll( lines );
+      }
+      typeTable = parseTypes(wlist);
+    }
+  }
+
+  @Override
+  public TokenFilter create(TokenStream input) {
+    return new WordDelimiterGraphFilter(input, typeTable == null ? WordDelimiterIterator.DEFAULT_WORD_DELIM_TABLE : typeTable,
+                                        flags, protectedWords);
+  }
+  
+  // source => type
+  private static Pattern typePattern = Pattern.compile( "(.*)\\s*=>\\s*(.*)\\s*$" );
+  
+  // parses a list of MappingCharFilter style rules into a custom byte[] type table
+  private byte[] parseTypes(List<String> rules) {
+    SortedMap<Character,Byte> typeMap = new TreeMap<>();
+    for( String rule : rules ){
+      Matcher m = typePattern.matcher(rule);
+      if( !m.find() )
+        throw new IllegalArgumentException("Invalid Mapping Rule : [" + rule + "]");
+      String lhs = parseString(m.group(1).trim());
+      Byte rhs = parseType(m.group(2).trim());
+      if (lhs.length() != 1)
+        throw new IllegalArgumentException("Invalid Mapping Rule : [" + rule + "]. Only a single character is allowed.");
+      if (rhs == null)
+        throw new IllegalArgumentException("Invalid Mapping Rule : [" + rule + "]. Illegal type.");
+      typeMap.put(lhs.charAt(0), rhs);
+    }
+    
+    // ensure the table is always at least as big as DEFAULT_WORD_DELIM_TABLE for performance
+    byte types[] = new byte[Math.max(typeMap.lastKey()+1, WordDelimiterIterator.DEFAULT_WORD_DELIM_TABLE.length)];
+    for (int i = 0; i < types.length; i++)
+      types[i] = WordDelimiterIterator.getType(i);
+    for (Map.Entry<Character,Byte> mapping : typeMap.entrySet())
+      types[mapping.getKey()] = mapping.getValue();
+    return types;
+  }
+  
+  private Byte parseType(String s) {
+    if (s.equals("LOWER"))
+      return LOWER;
+    else if (s.equals("UPPER"))
+      return UPPER;
+    else if (s.equals("ALPHA"))
+      return ALPHA;
+    else if (s.equals("DIGIT"))
+      return DIGIT;
+    else if (s.equals("ALPHANUM"))
+      return ALPHANUM;
+    else if (s.equals("SUBWORD_DELIM"))
+      return SUBWORD_DELIM;
+    else
+      return null;
+  }
+  
+  char[] out = new char[256];
+  
+  private String parseString(String s){
+    int readPos = 0;
+    int len = s.length();
+    int writePos = 0;
+    while( readPos < len ){
+      char c = s.charAt( readPos++ );
+      if( c == '\\' ){
+        if( readPos >= len )
+          throw new IllegalArgumentException("Invalid escaped char in [" + s + "]");
+        c = s.charAt( readPos++ );
+        switch( c ) {
+          case '\\' : c = '\\'; break;
+          case 'n' : c = '\n'; break;
+          case 't' : c = '\t'; break;
+          case 'r' : c = '\r'; break;
+          case 'b' : c = '\b'; break;
+          case 'f' : c = '\f'; break;
+          case 'u' :
+            if( readPos + 3 >= len )
+              throw new IllegalArgumentException("Invalid escaped char in [" + s + "]");
+            c = (char)Integer.parseInt( s.substring( readPos, readPos + 4 ), 16 );
+            readPos += 4;
+            break;
+        }
+      }
+      out[writePos++] = c;
+    }
+    return new String( out, 0, writePos );
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterIterator.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterIterator.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterIterator.java
index 0367dab..86b983d 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterIterator.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterIterator.java
@@ -16,15 +16,21 @@
  */
 package org.apache.lucene.analysis.miscellaneous;
 
-
-import static org.apache.lucene.analysis.miscellaneous.WordDelimiterFilter.*;
-
 /**
- * A BreakIterator-like API for iterating over subwords in text, according to WordDelimiterFilter rules.
+ * A BreakIterator-like API for iterating over subwords in text, according to WordDelimiterGraphFilter rules.
  * @lucene.internal
  */
 public final class WordDelimiterIterator {
 
+  static final int LOWER = 0x01;
+  static final int UPPER = 0x02;
+  static final int DIGIT = 0x04;
+  static final int SUBWORD_DELIM = 0x08;
+
+  // combinations: for testing, not for setting bits
+  public static final int ALPHA = 0x03;
+  public static final int ALPHANUM = 0x07;
+
   /** Indicates the end of iteration */
   public static final int DONE = -1;
   
@@ -97,7 +103,7 @@ public final class WordDelimiterIterator {
    * Create a new WordDelimiterIterator operating with the supplied rules.
    * 
    * @param charTypeTable table containing character types
-   * @param splitOnCaseChange if true, causes "PowerShot" to be two tokens; ("Power-Shot" remains two parts regards)
+   * @param splitOnCaseChange if true, causes "PowerShot" to be two tokens; ("Power-Shot" remains two parts regardless)
    * @param splitOnNumerics if true, causes "j2se" to be three tokens; "j" "2" "se"
    * @param stemEnglishPossessive if true, causes trailing "'s" to be removed for each subword: "O'Neil's" =&gt; "O", "Neil"
    */
@@ -323,4 +329,45 @@ public final class WordDelimiterIterator {
       default: return SUBWORD_DELIM;
     }
   }
-}
\ No newline at end of file
+
+  /**
+   * Checks if the given word type includes {@link #ALPHA}
+   *
+   * @param type Word type to check
+   * @return {@code true} if the type contains ALPHA, {@code false} otherwise
+   */
+  static boolean isAlpha(int type) {
+    return (type & ALPHA) != 0;
+  }
+
+  /**
+   * Checks if the given word type includes {@link #DIGIT}
+   *
+   * @param type Word type to check
+   * @return {@code true} if the type contains DIGIT, {@code false} otherwise
+   */
+  static boolean isDigit(int type) {
+    return (type & DIGIT) != 0;
+  }
+
+  /**
+   * Checks if the given word type includes {@link #SUBWORD_DELIM}
+   *
+   * @param type Word type to check
+   * @return {@code true} if the type contains SUBWORD_DELIM, {@code false} otherwise
+   */
+  static boolean isSubwordDelim(int type) {
+    return (type & SUBWORD_DELIM) != 0;
+  }
+
+  /**
+   * Checks if the given word type includes {@link #UPPER}
+   *
+   * @param type Word type to check
+   * @return {@code true} if the type contains UPPER, {@code false} otherwise
+   */
+  static boolean isUpper(int type) {
+    return (type & UPPER) != 0;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/FlattenGraphFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/FlattenGraphFilter.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/FlattenGraphFilter.java
deleted file mode 100644
index c1fa1f7..0000000
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/FlattenGraphFilter.java
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * 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.lucene.analysis.synonym;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.lucene.analysis.TokenFilter;
-import org.apache.lucene.analysis.TokenStream;
-import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
-import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
-import org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute;
-import org.apache.lucene.util.AttributeSource;
-import org.apache.lucene.util.RollingBuffer;
-
-/**
- * Converts an incoming graph token stream, such as one from
- * {@link SynonymGraphFilter}, into a flat form so that
- * all nodes form a single linear chain with no side paths.  Every
- * path through the graph touches every node.  This is necessary
- * when indexing a graph token stream, because the index does not
- * save {@link PositionLengthAttribute} and so it cannot
- * preserve the graph structure.  However, at search time,
- * query parsers can correctly handle the graph and this token
- * filter should <b>not</b> be used.
- *
- * <p>If the graph was not already flat to start, this
- * is likely a lossy process, i.e. it will often cause the 
- * graph to accept token sequences it should not, and to
- * reject token sequences it should not.
- *
- * <p>However, when applying synonyms during indexing, this
- * is necessary because Lucene already does not index a graph 
- * and so the indexing process is already lossy
- * (it ignores the {@link PositionLengthAttribute}).
- *
- * @lucene.experimental
- */
-public final class FlattenGraphFilter extends TokenFilter {
-
-  /** Holds all tokens leaving a given input position. */
-  private final static class InputNode implements RollingBuffer.Resettable {
-    private final List<AttributeSource.State> tokens = new ArrayList<>();
-
-    /** Our input node, or -1 if we haven't been assigned yet */
-    int node = -1;
-
-    /** Maximum to input node for all tokens leaving here; we use this
-     *  to know when we can freeze. */
-    int maxToNode = -1;
-
-    /** Where we currently map to; this changes (can only
-     *  increase as we see more input tokens), until we are finished
-     *  with this position. */
-    int outputNode = -1;
-
-    /** Which token (index into {@link #tokens}) we will next output. */
-    int nextOut;
-
-    @Override
-    public void reset() {
-      tokens.clear();
-      node = -1;
-      outputNode = -1;
-      maxToNode = -1;
-      nextOut = 0;
-    }
-  }
-
-  /** Gathers up merged input positions into a single output position,
-   *  only for the current "frontier" of nodes we've seen but can't yet
-   *  output because they are not frozen. */
-  private final static class OutputNode implements RollingBuffer.Resettable {
-    private final List<Integer> inputNodes = new ArrayList<>();
-
-    /** Node ID for this output, or -1 if we haven't been assigned yet. */
-    int node = -1;
-
-    /** Which input node (index into {@link #inputNodes}) we will next output. */
-    int nextOut;
-    
-    /** Start offset of tokens leaving this node. */
-    int startOffset = -1;
-
-    /** End offset of tokens arriving to this node. */
-    int endOffset = -1;
-
-    @Override
-    public void reset() {
-      inputNodes.clear();
-      node = -1;
-      nextOut = 0;
-      startOffset = -1;
-      endOffset = -1;
-    }
-  }
-
-  private final RollingBuffer<InputNode> inputNodes = new RollingBuffer<InputNode>() {
-    @Override
-    protected InputNode newInstance() {
-      return new InputNode();
-    }
-  };
-
-  private final RollingBuffer<OutputNode> outputNodes = new RollingBuffer<OutputNode>() {
-    @Override
-    protected OutputNode newInstance() {
-      return new OutputNode();
-    }
-  };
-
-  private final PositionIncrementAttribute posIncAtt = addAttribute(PositionIncrementAttribute.class);
-  private final PositionLengthAttribute posLenAtt = addAttribute(PositionLengthAttribute.class);
-  private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);
-
-  /** Which input node the last seen token leaves from */
-  private int inputFrom;
-
-  /** We are currently releasing tokens leaving from this output node */
-  private int outputFrom;
-
-  // for debugging:
-  //private int retOutputFrom;
-
-  private boolean done;
-
-  private int lastOutputFrom;
-
-  private int finalOffset;
-
-  private int finalPosInc;
-
-  private int maxLookaheadUsed;
-
-  private int lastStartOffset;
-
-  public FlattenGraphFilter(TokenStream in) {
-    super(in);
-  }
-
-  private boolean releaseBufferedToken() {
-
-    // We only need the while loop (retry) if we have a hole (an output node that has no tokens leaving):
-    while (outputFrom < outputNodes.getMaxPos()) {
-      OutputNode output = outputNodes.get(outputFrom);
-      if (output.inputNodes.isEmpty()) {
-        // No tokens arrived to this node, which happens for the first node
-        // after a hole:
-        //System.out.println("    skip empty outputFrom=" + outputFrom);
-        outputFrom++;
-        continue;
-      }
-
-      int maxToNode = -1;
-      for(int inputNodeID : output.inputNodes) {
-        InputNode inputNode = inputNodes.get(inputNodeID);
-        assert inputNode.outputNode == outputFrom;
-        maxToNode = Math.max(maxToNode, inputNode.maxToNode);
-      }
-      //System.out.println("  release maxToNode=" + maxToNode + " vs inputFrom=" + inputFrom);
-
-      // TODO: we could shrink the frontier here somewhat if we
-      // always output posLen=1 as part of our "sausagizing":
-      if (maxToNode <= inputFrom || done) {
-        //System.out.println("  output node merged these inputs: " + output.inputNodes);
-        // These tokens are now frozen
-        assert output.nextOut < output.inputNodes.size(): "output.nextOut=" + output.nextOut + " vs output.inputNodes.size()=" + output.inputNodes.size();
-        InputNode inputNode = inputNodes.get(output.inputNodes.get(output.nextOut));
-        if (done && inputNode.tokens.size() == 0 && outputFrom >= outputNodes.getMaxPos()) {
-          return false;
-        }
-        if (inputNode.tokens.size() == 0) {
-          assert inputNode.nextOut == 0;
-          assert output.nextOut == 0;
-          // Hole dest nodes should never be merged since 1) we always
-          // assign them to a new output position, and 2) since they never
-          // have arriving tokens they cannot be pushed:
-          assert output.inputNodes.size() == 1: output.inputNodes.size();
-          outputFrom++;
-          inputNodes.freeBefore(output.inputNodes.get(0));
-          outputNodes.freeBefore(outputFrom);
-          continue;
-        }
-
-        assert inputNode.nextOut < inputNode.tokens.size();
-
-        restoreState(inputNode.tokens.get(inputNode.nextOut));
-
-        // Correct posInc
-        assert outputFrom >= lastOutputFrom;
-        posIncAtt.setPositionIncrement(outputFrom - lastOutputFrom);
-        int toInputNodeID = inputNode.node + posLenAtt.getPositionLength();
-        InputNode toInputNode = inputNodes.get(toInputNodeID);
-
-        // Correct posLen
-        assert toInputNode.outputNode > outputFrom;
-        posLenAtt.setPositionLength(toInputNode.outputNode - outputFrom);
-        lastOutputFrom = outputFrom;
-        inputNode.nextOut++;
-        //System.out.println("  ret " + this);
-
-        OutputNode outputEndNode = outputNodes.get(toInputNode.outputNode);
-
-        // Correct offsets
-
-        // This is a bit messy; we must do this so offset don't go backwards,
-        // which would otherwise happen if the replacement has more tokens
-        // than the input:
-        int startOffset = Math.max(lastStartOffset, output.startOffset);
-
-        // We must do this in case the incoming tokens have broken offsets:
-        int endOffset = Math.max(startOffset, outputEndNode.endOffset);
-        
-        offsetAtt.setOffset(startOffset, endOffset);
-        lastStartOffset = startOffset;
-
-        if (inputNode.nextOut == inputNode.tokens.size()) {
-          output.nextOut++;
-          if (output.nextOut == output.inputNodes.size()) {
-            outputFrom++;
-            inputNodes.freeBefore(output.inputNodes.get(0));
-            outputNodes.freeBefore(outputFrom);
-          }
-        }
-
-        return true;
-      } else {
-        return false;
-      }
-    }
-
-    //System.out.println("    break false");
-    return false;
-  }
-
-  @Override
-  public boolean incrementToken() throws IOException {
-    //System.out.println("\nF.increment inputFrom=" + inputFrom + " outputFrom=" + outputFrom);
-
-    while (true) {
-      if (releaseBufferedToken()) {
-        //retOutputFrom += posIncAtt.getPositionIncrement();
-        //System.out.println("    return buffered: " + termAtt + " " + retOutputFrom + "-" + (retOutputFrom + posLenAtt.getPositionLength()));
-        //printStates();
-        return true;
-      } else if (done) {
-        //System.out.println("    done, return false");
-        return false;
-      }
-
-      if (input.incrementToken()) {
-        // Input node this token leaves from:
-        inputFrom += posIncAtt.getPositionIncrement();
-
-        int startOffset = offsetAtt.startOffset();
-        int endOffset = offsetAtt.endOffset();
-
-        // Input node this token goes to:
-        int inputTo = inputFrom + posLenAtt.getPositionLength();
-        //System.out.println("  input.inc " + termAtt + ": " + inputFrom + "-" + inputTo);
-
-        InputNode src = inputNodes.get(inputFrom);
-        if (src.node == -1) {
-          // This means the "from" node of this token was never seen as a "to" node,
-          // which should only happen if we just crossed a hole.  This is a challenging
-          // case for us because we normally rely on the full dependencies expressed
-          // by the arcs to assign outgoing node IDs.  It would be better if tokens
-          // were never dropped but instead just marked deleted with a new
-          // TermDeletedAttribute (boolean valued) ... but until that future, we have
-          // a hack here to forcefully jump the output node ID:
-          assert src.outputNode == -1;
-          src.node = inputFrom;
-
-          src.outputNode = outputNodes.getMaxPos() + 1;
-          //System.out.println("    hole: force to outputNode=" + src.outputNode);
-          OutputNode outSrc = outputNodes.get(src.outputNode);
-
-          // Not assigned yet:
-          assert outSrc.node == -1;
-          outSrc.node = src.outputNode;
-          outSrc.inputNodes.add(inputFrom);
-          outSrc.startOffset = startOffset;
-        } else {
-          OutputNode outSrc = outputNodes.get(src.outputNode);
-          if (outSrc.startOffset == -1 || startOffset > outSrc.startOffset) {
-            // "shrink wrap" the offsets so the original tokens (with most
-            // restrictive offsets) win:
-            outSrc.startOffset = Math.max(startOffset, outSrc.startOffset);
-          }
-        }
-
-        // Buffer this token:
-        src.tokens.add(captureState());
-        src.maxToNode = Math.max(src.maxToNode, inputTo);
-        maxLookaheadUsed = Math.max(maxLookaheadUsed, inputNodes.getBufferSize());
-
-        InputNode dest = inputNodes.get(inputTo);
-        if (dest.node == -1) {
-          // Common case: first time a token is arriving to this input position:
-          dest.node = inputTo;
-        }
-
-        // Always number output nodes sequentially:
-        int outputEndNode = src.outputNode + 1;
-
-        if (outputEndNode > dest.outputNode) {
-          if (dest.outputNode != -1) {
-            boolean removed = outputNodes.get(dest.outputNode).inputNodes.remove(Integer.valueOf(inputTo));
-            assert removed;
-          }
-          //System.out.println("    increase output node: " + dest.outputNode + " vs " + outputEndNode);
-          outputNodes.get(outputEndNode).inputNodes.add(inputTo);
-          dest.outputNode = outputEndNode;
-
-          // Since all we ever do is merge incoming nodes together, and then renumber
-          // the merged nodes sequentially, we should only ever assign smaller node
-          // numbers:
-          assert outputEndNode <= inputTo: "outputEndNode=" + outputEndNode + " vs inputTo=" + inputTo;
-        }
-
-        OutputNode outDest = outputNodes.get(dest.outputNode);
-        // "shrink wrap" the offsets so the original tokens (with most
-        // restrictive offsets) win:
-        if (outDest.endOffset == -1 || endOffset < outDest.endOffset) {
-          outDest.endOffset = endOffset;
-        }
-
-      } else {
-        //System.out.println("  got false from input");
-        input.end();
-        finalPosInc = posIncAtt.getPositionIncrement();
-        finalOffset = offsetAtt.endOffset();
-        done = true;
-        // Don't return false here: we need to force release any buffered tokens now
-      }
-    }
-  }
-
-  // Only for debugging:
-  /*
-  private void printStates() {
-    System.out.println("states:");
-    for(int i=outputFrom;i<outputNodes.getMaxPos();i++) {
-      OutputNode outputNode = outputNodes.get(i);
-      System.out.println("  output " + i + ": inputs " + outputNode.inputNodes);
-      for(int inputNodeID : outputNode.inputNodes) {
-        InputNode inputNode = inputNodes.get(inputNodeID);
-        assert inputNode.outputNode == i;
-      }
-    }
-  }
-  */
-
-  @Override
-  public void end() throws IOException {
-    if (done == false) {
-      super.end();
-    } else {
-      // NOTE, shady: don't call super.end, because we did already from incrementToken
-    }
-
-    clearAttributes();
-    if (done) {
-      // On exc, done is false, and we will not have set these:
-      posIncAtt.setPositionIncrement(finalPosInc);
-      offsetAtt.setOffset(finalOffset, finalOffset);
-    } else {
-      super.end();
-    }
-  }
-  
-  @Override
-  public void reset() throws IOException {
-    //System.out.println("F: reset");
-    super.reset();
-    inputFrom = -1;
-    inputNodes.reset();
-    InputNode in = inputNodes.get(0);
-    in.node = 0;
-    in.outputNode = 0;
-
-    outputNodes.reset();
-    OutputNode out = outputNodes.get(0);
-    out.node = 0;
-    out.inputNodes.add(0);
-    out.startOffset = 0;
-    outputFrom = 0;
-    //retOutputFrom = -1;
-    lastOutputFrom = -1;
-    done = false;
-    finalPosInc = -1;
-    finalOffset = -1;
-    lastStartOffset = 0;
-    maxLookaheadUsed = 0;
-  }
-
-  // for testing
-  int getMaxLookaheadUsed() {
-    return maxLookaheadUsed;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/FlattenGraphFilterFactory.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/FlattenGraphFilterFactory.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/FlattenGraphFilterFactory.java
deleted file mode 100644
index a6cba97..0000000
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/FlattenGraphFilterFactory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.lucene.analysis.synonym;
-
-import java.util.Map;
-
-import org.apache.lucene.analysis.TokenStream;
-import org.apache.lucene.analysis.util.TokenFilterFactory;
-
-/** 
- * Factory for {@link FlattenGraphFilter}. 
- *
- * @lucene.experimental
- */
-public class FlattenGraphFilterFactory extends TokenFilterFactory {
-
-  /** Creates a new FlattenGraphFilterFactory */
-  public FlattenGraphFilterFactory(Map<String,String> args) {
-    super(args);
-    if (!args.isEmpty()) {
-      throw new IllegalArgumentException("Unknown parameters: " + args);
-    }
-  }
-  
-  @Override
-  public TokenStream create(TokenStream input) {
-    return new FlattenGraphFilter(input);
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymFilter.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymFilter.java
index 29f6e1c..ec2676f 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymFilter.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymFilter.java
@@ -21,6 +21,7 @@ import java.util.Arrays;
 
 import org.apache.lucene.analysis.TokenFilter;
 import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.core.FlattenGraphFilter;
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymFilterFactory.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymFilterFactory.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymFilterFactory.java
index df10e9b..87ddc08 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymFilterFactory.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymFilterFactory.java
@@ -33,6 +33,7 @@ import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.LowerCaseFilter;
 import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.analysis.Tokenizer;
+import org.apache.lucene.analysis.core.FlattenGraphFilterFactory;
 import org.apache.lucene.analysis.core.WhitespaceTokenizer;
 import org.apache.lucene.analysis.util.ResourceLoader;
 import org.apache.lucene.analysis.util.ResourceLoaderAware;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymGraphFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymGraphFilter.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymGraphFilter.java
index 3d50e08..788db0a 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymGraphFilter.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymGraphFilter.java
@@ -17,8 +17,14 @@
 
 package org.apache.lucene.analysis.synonym;
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
 import org.apache.lucene.analysis.TokenFilter;
 import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.core.FlattenGraphFilter;
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
@@ -31,11 +37,6 @@ import org.apache.lucene.util.CharsRefBuilder;
 import org.apache.lucene.util.RollingBuffer;
 import org.apache.lucene.util.fst.FST;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
 // TODO: maybe we should resolve token -> wordID then run
 // FST on wordIDs, for better perf?
  

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/637915b8/lucene/analysis/common/src/resources/META-INF/services/org.apache.lucene.analysis.util.TokenFilterFactory
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/resources/META-INF/services/org.apache.lucene.analysis.util.TokenFilterFactory b/lucene/analysis/common/src/resources/META-INF/services/org.apache.lucene.analysis.util.TokenFilterFactory
index 5f8894c..4e33006 100644
--- a/lucene/analysis/common/src/resources/META-INF/services/org.apache.lucene.analysis.util.TokenFilterFactory
+++ b/lucene/analysis/common/src/resources/META-INF/services/org.apache.lucene.analysis.util.TokenFilterFactory
@@ -78,6 +78,7 @@ org.apache.lucene.analysis.miscellaneous.StemmerOverrideFilterFactory
 org.apache.lucene.analysis.miscellaneous.TrimFilterFactory
 org.apache.lucene.analysis.miscellaneous.TruncateTokenFilterFactory
 org.apache.lucene.analysis.miscellaneous.WordDelimiterFilterFactory
+org.apache.lucene.analysis.miscellaneous.WordDelimiterGraphFilterFactory
 org.apache.lucene.analysis.miscellaneous.ScandinavianFoldingFilterFactory
 org.apache.lucene.analysis.miscellaneous.ScandinavianNormalizationFilterFactory
 org.apache.lucene.analysis.ngram.EdgeNGramFilterFactory
@@ -103,6 +104,6 @@ org.apache.lucene.analysis.standard.StandardFilterFactory
 org.apache.lucene.analysis.sv.SwedishLightStemFilterFactory
 org.apache.lucene.analysis.synonym.SynonymFilterFactory
 org.apache.lucene.analysis.synonym.SynonymGraphFilterFactory
-org.apache.lucene.analysis.synonym.FlattenGraphFilterFactory
+org.apache.lucene.analysis.core.FlattenGraphFilterFactory
 org.apache.lucene.analysis.tr.TurkishLowerCaseFilterFactory
 org.apache.lucene.analysis.util.ElisionFilterFactory


[16/38] lucene-solr:jira/solr-9857: LUCENE-7619: don't let offsets go backwards

Posted by ab...@apache.org.
LUCENE-7619: don't let offsets go backwards


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

Branch: refs/heads/jira/solr-9857
Commit: 0bdcfc291fceab26e1c62a7e9791ce417671eacd
Parents: 39eec66
Author: Mike McCandless <mi...@apache.org>
Authored: Tue Jan 17 17:57:11 2017 -0500
Committer: Mike McCandless <mi...@apache.org>
Committed: Tue Jan 17 17:57:11 2017 -0500

----------------------------------------------------------------------
 .../miscellaneous/WordDelimiterGraphFilter.java   | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/0bdcfc29/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java
----------------------------------------------------------------------
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java
index ea6f6cd..fe8ed72 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.java
@@ -195,6 +195,7 @@ public final class WordDelimiterGraphFilter extends TokenFilter {
   private int savedStartOffset;
   private int savedEndOffset;
   private AttributeSource.State savedState;
+  private int lastStartOffset;
   
   // if length by start + end offsets doesn't match the term text then assume
   // this is a synonym and don't adjust the offsets.
@@ -373,12 +374,24 @@ public final class WordDelimiterGraphFilter extends TokenFilter {
         int endPart = bufferedParts[4*bufferedPos+3];
         bufferedPos++;
 
+        int startOffset;
+        int endOffset;
+
         if (hasIllegalOffsets) {
-          offsetAttribute.setOffset(savedStartOffset, savedEndOffset);
+          startOffset = savedStartOffset;
+          endOffset = savedEndOffset;
         } else {
-          offsetAttribute.setOffset(savedStartOffset + startPart, savedStartOffset + endPart);
+          startOffset = savedStartOffset + startPart;
+          endOffset = savedStartOffset + endPart;
         }
 
+        // never let offsets go backwards:
+        startOffset = Math.max(startOffset, lastStartOffset);
+        endOffset = Math.max(endOffset, lastStartOffset);
+
+        offsetAttribute.setOffset(startOffset, endOffset);
+        lastStartOffset = startOffset;
+
         if (termPart == null) {
           termAttribute.copyBuffer(savedTermBuffer, startPart, endPart - startPart);
         } else {
@@ -402,6 +415,7 @@ public final class WordDelimiterGraphFilter extends TokenFilter {
     super.reset();
     accumPosInc = 0;
     savedState = null;
+    lastStartOffset = 0;
     concat.clear();
     concatAll.clear();
   }


[24/38] lucene-solr:jira/solr-9857: Fix changes entry formatting (sub-bullets wrongly promoted to top level items); don't interpret parenthesized text starting with 'e.g.' as an attribution.

Posted by ab...@apache.org.
Fix changes entry formatting (sub-bullets wrongly promoted to top level items); don't interpret parenthesized text starting with 'e.g.' as an attribution.


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

Branch: refs/heads/jira/solr-9857
Commit: 85061e39e063f99fedcfeee1b2de6cdb8080daa4
Parents: 8c2ef3b
Author: Steve Rowe <sa...@apache.org>
Authored: Wed Jan 18 15:21:23 2017 -0500
Committer: Steve Rowe <sa...@apache.org>
Committed: Wed Jan 18 15:21:23 2017 -0500

----------------------------------------------------------------------
 lucene/site/changes/changes2html.pl |  3 ++-
 solr/CHANGES.txt                    | 16 ++++++++--------
 2 files changed, 10 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/85061e39/lucene/site/changes/changes2html.pl
----------------------------------------------------------------------
diff --git a/lucene/site/changes/changes2html.pl b/lucene/site/changes/changes2html.pl
index 11a0fab..5b866fc 100755
--- a/lucene/site/changes/changes2html.pl
+++ b/lucene/site/changes/changes2html.pl
@@ -26,7 +26,6 @@ use warnings;
 
 my $jira_url_prefix = 'http://issues.apache.org/jira/browse/';
 my $github_pull_request_prefix = 'https://github.com/apache/lucene-solr/pull/';
-my $bugzilla_url_prefix = 'http://issues.apache.org/bugzilla/show_bug.cgi?id=';
 my $month_regex = &setup_month_regex;
 my %month_nums = &setup_month_nums;
 my %lucene_bugzilla_jira_map = &setup_lucene_bugzilla_jira_map;
@@ -643,6 +642,7 @@ sub markup_trailing_attribution {
                            (?!inverse\ )
                            (?![Tt]he\ )
                            (?!use\ the\ bug\ number)
+                           (?!e\.?g\.?\b)
                      [^()"]+?\))\s*$}
                     {\n${extra_newline}<span class="attrib">$1</span>}x) {
     # If attribution is not found, then look for attribution with a
@@ -668,6 +668,7 @@ sub markup_trailing_attribution {
                       (?!inverse\ )
                       (?![Tt]he\ )
                       (?!use\ the\ bug\ number)
+                      (?!e\.?g\.?\b)
                  [^()"]+?\)))
                 ((?:\.|(?i:\.?\s*Issue\s+\d{3,}|LUCENE-\d+)\.?)\s*)$}
               {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/85061e39/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index cfd7a4c..1f32c24 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -225,15 +225,15 @@ New Features
 
 * SOLR-9805: Use metrics-jvm library to instrument jvm internals such as GC, memory usage and others. (shalin)
 
-* SOLR-9812: SOLR-9911, SOLR-9960: Added a new /admin/metrics API to return all metrics collected by Solr via API.
+* SOLR-9812, SOLR-9911, SOLR-9960: Added a new /admin/metrics API to return all metrics collected by Solr via API.
   API supports four optional multi-valued parameters:
-  * 'group' (all,jvm,jetty,node,core),
-  * 'type' (all,counter,timer,gauge,histogram),
-  * 'prefix' that filters the returned metrics,
-  * 'registry' that selects one or more registries by prefix (eg. solr.jvm,solr.core.collection1)
-  Example: http://localhost:8983/solr/admin/metrics?group=jvm,jetty&type=counter
-  Example: http://localhost:8983/solr/admin/metrics?group=jvm&prefix=buffers,os
-  Example: http://localhost:8983/solr/admin/metrics?registry=solr.node,solr.core&prefix=ADMIN
+  - 'group' (all,jvm,jetty,node,core),
+  - 'type' (all,counter,timer,gauge,histogram),
+  - 'prefix' that filters the returned metrics,
+  - 'registry' that selects one or more registries by prefix (eg. solr.jvm,solr.core.collection1)
+  - Example: http://localhost:8983/solr/admin/metrics?group=jvm,jetty&type=counter
+  - Example: http://localhost:8983/solr/admin/metrics?group=jvm&prefix=buffers,os
+  - Example: http://localhost:8983/solr/admin/metrics?registry=solr.node,solr.core&prefix=ADMIN
   (shalin, ab)
 
 * SOLR-9884: Add version to segments handler output (Steven Bower via Erick Erickson)


[20/38] lucene-solr:jira/solr-9857: LUCENE-7640: Fix test bug.

Posted by ab...@apache.org.
LUCENE-7640: Fix test bug.


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

Branch: refs/heads/jira/solr-9857
Commit: 188a19e67e8e4a9d2c7f0e596eb0820b80770d98
Parents: 71aa463
Author: Adrien Grand <jp...@gmail.com>
Authored: Wed Jan 18 16:59:26 2017 +0100
Committer: Adrien Grand <jp...@gmail.com>
Committed: Wed Jan 18 16:59:26 2017 +0100

----------------------------------------------------------------------
 .../apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/188a19e6/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java b/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
index 3a08bfa..4287273 100644
--- a/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
+++ b/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
@@ -256,8 +256,8 @@ public class TestLucene60PointsFormat extends BasePointsFormatTestCase {
           @Override
           public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
             for (int dim = 0; dim < 2; ++dim) {
-              if (StringHelper.compare(3, uniquePointValue[0], 0, maxPackedValue, dim * 3) > 0 ||
-                  StringHelper.compare(3, uniquePointValue[0], 0, minPackedValue, dim * 3) < 0) {
+              if (StringHelper.compare(3, uniquePointValue[dim], 0, maxPackedValue, dim * 3) > 0 ||
+                  StringHelper.compare(3, uniquePointValue[dim], 0, minPackedValue, dim * 3) < 0) {
                 return Relation.CELL_OUTSIDE_QUERY;
               }
             }


[35/38] lucene-solr:jira/solr-9857: LUCENE-7055: Make sure to use the same reader to create the weight and pull the scorers.

Posted by ab...@apache.org.
LUCENE-7055: Make sure to use the same reader to create the weight and pull the scorers.


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

Branch: refs/heads/jira/solr-9857
Commit: e8fa59904c99b7c09a89a4b2f79699ff5a384115
Parents: 075aec9
Author: Adrien Grand <jp...@gmail.com>
Authored: Thu Jan 19 09:29:51 2017 +0100
Committer: Adrien Grand <jp...@gmail.com>
Committed: Thu Jan 19 09:30:34 2017 +0100

----------------------------------------------------------------------
 .../test/org/apache/lucene/search/TestIndexOrDocValuesQuery.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e8fa5990/lucene/sandbox/src/test/org/apache/lucene/search/TestIndexOrDocValuesQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestIndexOrDocValuesQuery.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestIndexOrDocValuesQuery.java
index 2a16e5d..de289e7 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/search/TestIndexOrDocValuesQuery.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestIndexOrDocValuesQuery.java
@@ -68,7 +68,7 @@ public class TestIndexOrDocValuesQuery extends LuceneTestCase {
         .build();
 
     final Weight w1 = searcher.createNormalizedWeight(q1, random().nextBoolean());
-    final Scorer s1 = w1.scorer(reader.leaves().get(0));
+    final Scorer s1 = w1.scorer(searcher.getIndexReader().leaves().get(0));
     assertNotNull(s1.twoPhaseIterator()); // means we use doc values
 
     // The term query is less selective, so the IndexOrDocValuesQuery should use points
@@ -78,7 +78,7 @@ public class TestIndexOrDocValuesQuery extends LuceneTestCase {
         .build();
 
     final Weight w2 = searcher.createNormalizedWeight(q2, random().nextBoolean());
-    final Scorer s2 = w2.scorer(reader.leaves().get(0));
+    final Scorer s2 = w2.scorer(searcher.getIndexReader().leaves().get(0));
     assertNull(s2.twoPhaseIterator()); // means we use points
 
     reader.close();


[07/38] lucene-solr:jira/solr-9857: LUCENE-7055: Add ScorerProvider to get an estimation of the cost of scorers before building them.

Posted by ab...@apache.org.
LUCENE-7055: Add ScorerProvider to get an estimation of the cost of scorers before building them.


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

Branch: refs/heads/jira/solr-9857
Commit: 86233cb95de6f24aa2ae7fd016b7d75d535024c7
Parents: 38af094
Author: Adrien Grand <jp...@gmail.com>
Authored: Mon Jan 16 15:47:53 2017 +0100
Committer: Adrien Grand <jp...@gmail.com>
Committed: Tue Jan 17 08:51:58 2017 +0100

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   7 +
 .../codecs/simpletext/SimpleTextBKDReader.java  |  50 +++
 .../org/apache/lucene/codecs/PointsWriter.java  |   5 +
 .../org/apache/lucene/index/CheckIndex.java     |  37 +++
 .../org/apache/lucene/index/PointValues.java    |   7 +
 .../apache/lucene/index/PointValuesWriter.java  |  10 +
 .../apache/lucene/index/SortingLeafReader.java  |   5 +
 .../lucene/search/Boolean2ScorerSupplier.java   | 217 ++++++++++++
 .../org/apache/lucene/search/BooleanWeight.java | 136 ++------
 .../apache/lucene/search/ConjunctionDISI.java   |   2 +-
 .../apache/lucene/search/ConjunctionScorer.java |   3 +-
 .../lucene/search/ConstantScoreQuery.java       |  46 ++-
 .../lucene/search/MinShouldMatchSumScorer.java  |  22 +-
 .../apache/lucene/search/PointRangeQuery.java   | 144 +++++---
 .../apache/lucene/search/ScorerSupplier.java    |  47 +++
 .../java/org/apache/lucene/search/Weight.java   |  25 ++
 .../org/apache/lucene/util/bkd/BKDReader.java   |  59 ++++
 .../search/TestBoolean2ScorerSupplier.java      | 332 +++++++++++++++++++
 .../search/TestBooleanQueryVisitSubscorers.java |   4 +-
 .../apache/lucene/search/TestFilterWeight.java  |   3 +-
 .../apache/lucene/util/TestDocIdSetBuilder.java |   5 +
 .../util/bkd/TestMutablePointsReaderUtils.java  |   5 +
 .../apache/lucene/index/memory/MemoryIndex.java |   5 +
 .../lucene/search/DocValuesRangeQuery.java      |  11 +-
 .../lucene/search/IndexOrDocValuesQuery.java    | 116 +++++++
 .../search/TestIndexOrDocValuesQuery.java       |  89 +++++
 .../codecs/cranky/CrankyPointsFormat.java       |   5 +
 .../lucene/index/AssertingLeafReader.java       |   7 +
 .../apache/lucene/search/AssertingWeight.java   |  42 ++-
 29 files changed, 1248 insertions(+), 198 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 851ed72..59992ea 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -73,6 +73,13 @@ Bug Fixes
 * LUCENE-7630: Fix (Edge)NGramTokenFilter to no longer drop payloads
   and preserve all attributes. (Nathan Gass via Uwe Schindler)
 
+Improvements
+
+* LUCENE-7055: Added Weight#scorerSupplier, which allows to estimate the cost
+  of a Scorer before actually building it, in order to optimize how the query
+  should be run, eg. using points or doc values depending on costs of other
+  parts of the query. (Adrien Grand)
+
 ======================= Lucene 6.4.0 =======================
 
 API Changes

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextBKDReader.java
----------------------------------------------------------------------
diff --git a/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextBKDReader.java b/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextBKDReader.java
index 488547b..b7af45a 100644
--- a/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextBKDReader.java
+++ b/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextBKDReader.java
@@ -286,6 +286,56 @@ final class SimpleTextBKDReader extends PointValues implements Accountable {
     }
   }
 
+  @Override
+  public long estimatePointCount(IntersectVisitor visitor) {
+    return estimatePointCount(getIntersectState(visitor), 1, minPackedValue, maxPackedValue);
+  }
+
+  private long estimatePointCount(IntersectState state,
+      int nodeID, byte[] cellMinPacked, byte[] cellMaxPacked) {
+    Relation r = state.visitor.compare(cellMinPacked, cellMaxPacked);
+
+    if (r == Relation.CELL_OUTSIDE_QUERY) {
+      // This cell is fully outside of the query shape: stop recursing
+      return 0L;
+    } else if (nodeID >= leafNodeOffset) {
+      // Assume all points match and there are no dups
+      return maxPointsInLeafNode;
+    } else {
+      
+      // Non-leaf node: recurse on the split left and right nodes
+
+      int address = nodeID * bytesPerIndexEntry;
+      int splitDim;
+      if (numDims == 1) {
+        splitDim = 0;
+      } else {
+        splitDim = splitPackedValues[address++] & 0xff;
+      }
+      
+      assert splitDim < numDims;
+
+      // TODO: can we alloc & reuse this up front?
+
+      byte[] splitPackedValue = new byte[packedBytesLength];
+
+      // Recurse on left sub-tree:
+      System.arraycopy(cellMaxPacked, 0, splitPackedValue, 0, packedBytesLength);
+      System.arraycopy(splitPackedValues, address, splitPackedValue, splitDim*bytesPerDim, bytesPerDim);
+      final long leftCost = estimatePointCount(state,
+                2*nodeID,
+                cellMinPacked, splitPackedValue);
+
+      // Recurse on right sub-tree:
+      System.arraycopy(cellMinPacked, 0, splitPackedValue, 0, packedBytesLength);
+      System.arraycopy(splitPackedValues, address, splitPackedValue, splitDim*bytesPerDim, bytesPerDim);
+      final long rightCost = estimatePointCount(state,
+                2*nodeID+1,
+                splitPackedValue, cellMaxPacked);
+      return leftCost + rightCost;
+    }
+  }
+
   /** Copies the split value for this node into the provided byte array */
   public void copySplitValue(int nodeID, byte[] splitPackedValue) {
     int address = nodeID * bytesPerIndexEntry;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/codecs/PointsWriter.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/codecs/PointsWriter.java b/lucene/core/src/java/org/apache/lucene/codecs/PointsWriter.java
index 38cd440..d9a0b30 100644
--- a/lucene/core/src/java/org/apache/lucene/codecs/PointsWriter.java
+++ b/lucene/core/src/java/org/apache/lucene/codecs/PointsWriter.java
@@ -128,6 +128,11 @@ public abstract class PointsWriter implements Closeable {
               }
 
               @Override
+              public long estimatePointCount(IntersectVisitor visitor) {
+                throw new UnsupportedOperationException();
+              }
+
+              @Override
               public byte[] getMinPackedValue() {
                 throw new UnsupportedOperationException();
               }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java b/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
index 3bb10d3..7611a7f 100644
--- a/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
+++ b/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java
@@ -42,6 +42,8 @@ import org.apache.lucene.codecs.TermVectorsReader;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.DocumentStoredFieldVisitor;
 import org.apache.lucene.index.CheckIndex.Status.DocValuesStatus;
+import org.apache.lucene.index.PointValues.IntersectVisitor;
+import org.apache.lucene.index.PointValues.Relation;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.LeafFieldComparator;
 import org.apache.lucene.search.Sort;
@@ -1810,6 +1812,19 @@ public final class CheckIndex implements Closeable {
             long size = values.size();
             int docCount = values.getDocCount();
 
+            final long crossCost = values.estimatePointCount(new ConstantRelationIntersectVisitor(Relation.CELL_CROSSES_QUERY));
+            if (crossCost < size) {
+              throw new RuntimeException("estimatePointCount should return >= size when all cells match");
+            }
+            final long insideCost = values.estimatePointCount(new ConstantRelationIntersectVisitor(Relation.CELL_INSIDE_QUERY));
+            if (insideCost < size) {
+              throw new RuntimeException("estimatePointCount should return >= size when all cells fully match");
+            }
+            final long outsideCost = values.estimatePointCount(new ConstantRelationIntersectVisitor(Relation.CELL_OUTSIDE_QUERY));
+            if (outsideCost != 0) {
+              throw new RuntimeException("estimatePointCount should return 0 when no cells match");
+            }
+
             VerifyPointsVisitor visitor = new VerifyPointsVisitor(fieldInfo.name, reader.maxDoc(), values);
             values.intersect(visitor);
 
@@ -2002,6 +2017,28 @@ public final class CheckIndex implements Closeable {
     }
   }
 
+  private static class ConstantRelationIntersectVisitor implements IntersectVisitor {
+    private final Relation relation;
+
+    ConstantRelationIntersectVisitor(Relation relation) {
+      this.relation = relation;
+    }
+
+    @Override
+    public void visit(int docID) throws IOException {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void visit(int docID, byte[] packedValue) throws IOException {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+      return relation;
+    }
+  }
   
   /**
    * Test stored fields.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/index/PointValues.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/PointValues.java b/lucene/core/src/java/org/apache/lucene/index/PointValues.java
index ffac5f7..01f77e4 100644
--- a/lucene/core/src/java/org/apache/lucene/index/PointValues.java
+++ b/lucene/core/src/java/org/apache/lucene/index/PointValues.java
@@ -26,6 +26,7 @@ import org.apache.lucene.document.Field;
 import org.apache.lucene.document.FloatPoint;
 import org.apache.lucene.document.IntPoint;
 import org.apache.lucene.document.LongPoint;
+import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.util.StringHelper;
 import org.apache.lucene.util.bkd.BKDWriter;
 
@@ -220,6 +221,12 @@ public abstract class PointValues {
    *  to test whether each document is deleted, if necessary. */
   public abstract void intersect(IntersectVisitor visitor) throws IOException;
 
+  /** Estimate the number of points that would be visited by {@link #intersect}
+   * with the given {@link IntersectVisitor}. This should run many times faster
+   * than {@link #intersect(IntersectVisitor)}.
+   * @see DocIdSetIterator#cost */
+  public abstract long estimatePointCount(IntersectVisitor visitor);
+
   /** Returns minimum value for each dimension, packed, or null if {@link #size} is <code>0</code> */
   public abstract byte[] getMinPackedValue() throws IOException;
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/index/PointValuesWriter.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/PointValuesWriter.java b/lucene/core/src/java/org/apache/lucene/index/PointValuesWriter.java
index 07cf293..4aaf095 100644
--- a/lucene/core/src/java/org/apache/lucene/index/PointValuesWriter.java
+++ b/lucene/core/src/java/org/apache/lucene/index/PointValuesWriter.java
@@ -91,6 +91,11 @@ class PointValuesWriter {
       }
 
       @Override
+      public long estimatePointCount(IntersectVisitor visitor) {
+        throw new UnsupportedOperationException();
+      }
+
+      @Override
       public byte[] getMinPackedValue() {
         throw new UnsupportedOperationException();
       }
@@ -209,6 +214,11 @@ class PointValuesWriter {
     }
 
     @Override
+    public long estimatePointCount(IntersectVisitor visitor) {
+      return in.estimatePointCount(visitor);
+    }
+
+    @Override
     public byte[] getMinPackedValue() throws IOException {
       return in.getMinPackedValue();
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/index/SortingLeafReader.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/SortingLeafReader.java b/lucene/core/src/java/org/apache/lucene/index/SortingLeafReader.java
index a6748b8..b36b284 100644
--- a/lucene/core/src/java/org/apache/lucene/index/SortingLeafReader.java
+++ b/lucene/core/src/java/org/apache/lucene/index/SortingLeafReader.java
@@ -328,6 +328,11 @@ class SortingLeafReader extends FilterLeafReader {
     }
 
     @Override
+    public long estimatePointCount(IntersectVisitor visitor) {
+      return in.estimatePointCount(visitor);
+    }
+
+    @Override
     public byte[] getMinPackedValue() throws IOException {
       return in.getMinPackedValue();
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/search/Boolean2ScorerSupplier.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/Boolean2ScorerSupplier.java b/lucene/core/src/java/org/apache/lucene/search/Boolean2ScorerSupplier.java
new file mode 100644
index 0000000..4540c85
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/search/Boolean2ScorerSupplier.java
@@ -0,0 +1,217 @@
+/*
+ * 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.lucene.search;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.OptionalLong;
+import java.util.stream.Stream;
+
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.util.PriorityQueue;
+
+final class Boolean2ScorerSupplier extends ScorerSupplier {
+
+  private final BooleanWeight weight;
+  private final Map<BooleanClause.Occur, Collection<ScorerSupplier>> subs;
+  private final boolean needsScores;
+  private final int minShouldMatch;
+  private long cost = -1;
+
+  Boolean2ScorerSupplier(BooleanWeight weight,
+      Map<Occur, Collection<ScorerSupplier>> subs,
+      boolean needsScores, int minShouldMatch) {
+    if (minShouldMatch < 0) {
+      throw new IllegalArgumentException("minShouldMatch must be positive, but got: " + minShouldMatch);
+    }
+    if (minShouldMatch != 0 && minShouldMatch >= subs.get(Occur.SHOULD).size()) {
+      throw new IllegalArgumentException("minShouldMatch must be strictly less than the number of SHOULD clauses");
+    }
+    if (needsScores == false && minShouldMatch == 0 && subs.get(Occur.SHOULD).size() > 0
+        && subs.get(Occur.MUST).size() + subs.get(Occur.FILTER).size() > 0) {
+      throw new IllegalArgumentException("Cannot pass purely optional clauses if scores are not needed");
+    }
+    if (subs.get(Occur.SHOULD).size() + subs.get(Occur.MUST).size() + subs.get(Occur.FILTER).size() == 0) {
+      throw new IllegalArgumentException("There should be at least one positive clause");
+    }
+    this.weight = weight;
+    this.subs = subs;
+    this.needsScores = needsScores;
+    this.minShouldMatch = minShouldMatch;
+  }
+
+  private long computeCost() {
+    OptionalLong minRequiredCost = Stream.concat(
+        subs.get(Occur.MUST).stream(),
+        subs.get(Occur.FILTER).stream())
+        .mapToLong(ScorerSupplier::cost)
+        .min();
+    if (minRequiredCost.isPresent() && minShouldMatch == 0) {
+      return minRequiredCost.getAsLong();
+    } else {
+      final Collection<ScorerSupplier> optionalScorers = subs.get(Occur.SHOULD);
+      final long shouldCost = MinShouldMatchSumScorer.cost(
+          optionalScorers.stream().mapToLong(ScorerSupplier::cost),
+          optionalScorers.size(), minShouldMatch);
+      return Math.min(minRequiredCost.orElse(Long.MAX_VALUE), shouldCost);
+    }
+  }
+
+  @Override
+  public long cost() {
+    if (cost == -1) {
+      cost = computeCost();
+    }
+    return cost;
+  }
+
+  @Override
+  public Scorer get(boolean randomAccess) throws IOException {
+    // three cases: conjunction, disjunction, or mix
+
+    // pure conjunction
+    if (subs.get(Occur.SHOULD).isEmpty()) {
+      return excl(req(subs.get(Occur.FILTER), subs.get(Occur.MUST), randomAccess), subs.get(Occur.MUST_NOT));
+    }
+
+    // pure disjunction
+    if (subs.get(Occur.FILTER).isEmpty() && subs.get(Occur.MUST).isEmpty()) {
+      return excl(opt(subs.get(Occur.SHOULD), minShouldMatch, needsScores, randomAccess), subs.get(Occur.MUST_NOT));
+    }
+
+    // conjunction-disjunction mix:
+    // we create the required and optional pieces, and then
+    // combine the two: if minNrShouldMatch > 0, then it's a conjunction: because the
+    // optional side must match. otherwise it's required + optional
+
+    if (minShouldMatch > 0) {
+      boolean reqRandomAccess = true;
+      boolean msmRandomAccess = true;
+      if (randomAccess == false) {
+        // We need to figure out whether the MUST/FILTER or the SHOULD clauses would lead the iteration
+        final long reqCost = Stream.concat(
+            subs.get(Occur.MUST).stream(),
+            subs.get(Occur.FILTER).stream())
+            .mapToLong(ScorerSupplier::cost)
+            .min().getAsLong();
+        final long msmCost = MinShouldMatchSumScorer.cost(
+            subs.get(Occur.SHOULD).stream().mapToLong(ScorerSupplier::cost),
+            subs.get(Occur.SHOULD).size(), minShouldMatch);
+        reqRandomAccess = reqCost > msmCost;
+        msmRandomAccess = msmCost > reqCost;
+      }
+      Scorer req = excl(req(subs.get(Occur.FILTER), subs.get(Occur.MUST), reqRandomAccess), subs.get(Occur.MUST_NOT));
+      Scorer opt = opt(subs.get(Occur.SHOULD), minShouldMatch, needsScores, msmRandomAccess);
+      return new ConjunctionScorer(weight, Arrays.asList(req, opt), Arrays.asList(req, opt));
+    } else {
+      assert needsScores;
+      return new ReqOptSumScorer(
+          excl(req(subs.get(Occur.FILTER), subs.get(Occur.MUST), randomAccess), subs.get(Occur.MUST_NOT)),
+          opt(subs.get(Occur.SHOULD), minShouldMatch, needsScores, true));
+    }
+  }
+
+  /** Create a new scorer for the given required clauses. Note that
+   *  {@code requiredScoring} is a subset of {@code required} containing
+   *  required clauses that should participate in scoring. */
+  private Scorer req(Collection<ScorerSupplier> requiredNoScoring, Collection<ScorerSupplier> requiredScoring, boolean randomAccess) throws IOException {
+    if (requiredNoScoring.size() + requiredScoring.size() == 1) {
+      Scorer req = (requiredNoScoring.isEmpty() ? requiredScoring : requiredNoScoring).iterator().next().get(randomAccess);
+
+      if (needsScores == false) {
+        return req;
+      }
+
+      if (requiredScoring.isEmpty()) {
+        // Scores are needed but we only have a filter clause
+        // BooleanWeight expects that calling score() is ok so we need to wrap
+        // to prevent score() from being propagated
+        return new FilterScorer(req) {
+          @Override
+          public float score() throws IOException {
+            return 0f;
+          }
+          @Override
+          public int freq() throws IOException {
+            return 0;
+          }
+        };
+      }
+
+      return req;
+    } else {
+      long minCost = Math.min(
+          requiredNoScoring.stream().mapToLong(ScorerSupplier::cost).min().orElse(Long.MAX_VALUE),
+          requiredScoring.stream().mapToLong(ScorerSupplier::cost).min().orElse(Long.MAX_VALUE));
+      List<Scorer> requiredScorers = new ArrayList<>();
+      List<Scorer> scoringScorers = new ArrayList<>();
+      for (ScorerSupplier s : requiredNoScoring) {
+        requiredScorers.add(s.get(randomAccess || s.cost() > minCost));
+      }
+      for (ScorerSupplier s : requiredScoring) {
+        Scorer scorer = s.get(randomAccess || s.cost() > minCost);
+        requiredScorers.add(scorer);
+        scoringScorers.add(scorer);
+      }
+      return new ConjunctionScorer(weight, requiredScorers, scoringScorers);
+    }
+  }
+
+  private Scorer excl(Scorer main, Collection<ScorerSupplier> prohibited) throws IOException {
+    if (prohibited.isEmpty()) {
+      return main;
+    } else {
+      return new ReqExclScorer(main, opt(prohibited, 1, false, true));
+    }
+  }
+
+  private Scorer opt(Collection<ScorerSupplier> optional, int minShouldMatch,
+      boolean needsScores, boolean randomAccess) throws IOException {
+    if (optional.size() == 1) {
+      return optional.iterator().next().get(randomAccess);
+    } else if (minShouldMatch > 1) {
+      final List<Scorer> optionalScorers = new ArrayList<>();
+      final PriorityQueue<ScorerSupplier> pq = new PriorityQueue<ScorerSupplier>(subs.get(Occur.SHOULD).size() - minShouldMatch + 1) {
+        @Override
+        protected boolean lessThan(ScorerSupplier a, ScorerSupplier b) {
+          return a.cost() > b.cost();
+        }
+      };
+      for (ScorerSupplier scorer : subs.get(Occur.SHOULD)) {
+        ScorerSupplier overflow = pq.insertWithOverflow(scorer);
+        if (overflow != null) {
+          optionalScorers.add(overflow.get(true));
+        }
+      }
+      for (ScorerSupplier scorer : pq) {
+        optionalScorers.add(scorer.get(randomAccess));
+      }
+      return new MinShouldMatchSumScorer(weight, optionalScorers, minShouldMatch);
+    } else {
+      final List<Scorer> optionalScorers = new ArrayList<>();
+      for (ScorerSupplier scorer : optional) {
+        optionalScorers.add(scorer.get(randomAccess));
+      }
+      return new DisjunctionSumScorer(weight, optionalScorers, needsScores);
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/search/BooleanWeight.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/BooleanWeight.java b/lucene/core/src/java/org/apache/lucene/search/BooleanWeight.java
index ce4419f..dc44d53 100644
--- a/lucene/core/src/java/org/apache/lucene/search/BooleanWeight.java
+++ b/lucene/core/src/java/org/apache/lucene/search/BooleanWeight.java
@@ -19,9 +19,11 @@ package org.apache.lucene.search;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.lucene.index.LeafReaderContext;
@@ -265,7 +267,9 @@ final class BooleanWeight extends Weight {
     if (prohibited.isEmpty()) {
       return positiveScorer;
     } else {
-      Scorer prohibitedScorer = opt(prohibited, 1);
+      Scorer prohibitedScorer = prohibited.size() == 1
+          ? prohibited.get(0)
+          : new DisjunctionSumScorer(this, prohibited, false);
       if (prohibitedScorer.twoPhaseIterator() != null) {
         // ReqExclBulkScorer can't deal efficiently with two-phased prohibited clauses
         return null;
@@ -288,50 +292,48 @@ final class BooleanWeight extends Weight {
 
   @Override
   public Scorer scorer(LeafReaderContext context) throws IOException {
-    // initially the user provided value,
-    // but if minNrShouldMatch == optional.size(),
-    // we will optimize and move these to required, making this 0
+    ScorerSupplier scorerSupplier = scorerSupplier(context);
+    if (scorerSupplier == null) {
+      return null;
+    }
+    return scorerSupplier.get(false);
+  }
+
+  @Override
+  public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
     int minShouldMatch = query.getMinimumNumberShouldMatch();
 
-    List<Scorer> required = new ArrayList<>();
-    // clauses that are required AND participate in scoring, subset of 'required'
-    List<Scorer> requiredScoring = new ArrayList<>();
-    List<Scorer> prohibited = new ArrayList<>();
-    List<Scorer> optional = new ArrayList<>();
+    final Map<Occur, Collection<ScorerSupplier>> scorers = new EnumMap<>(Occur.class);
+    for (Occur occur : Occur.values()) {
+      scorers.put(occur, new ArrayList<>());
+    }
+
     Iterator<BooleanClause> cIter = query.iterator();
     for (Weight w  : weights) {
       BooleanClause c =  cIter.next();
-      Scorer subScorer = w.scorer(context);
+      ScorerSupplier subScorer = w.scorerSupplier(context);
       if (subScorer == null) {
         if (c.isRequired()) {
           return null;
         }
-      } else if (c.isRequired()) {
-        required.add(subScorer);
-        if (c.isScoring()) {
-          requiredScoring.add(subScorer);
-        }
-      } else if (c.isProhibited()) {
-        prohibited.add(subScorer);
       } else {
-        optional.add(subScorer);
+        scorers.get(c.getOccur()).add(subScorer);
       }
     }
-    
+
     // scorer simplifications:
     
-    if (optional.size() == minShouldMatch) {
+    if (scorers.get(Occur.SHOULD).size() == minShouldMatch) {
       // any optional clauses are in fact required
-      required.addAll(optional);
-      requiredScoring.addAll(optional);
-      optional.clear();
+      scorers.get(Occur.MUST).addAll(scorers.get(Occur.SHOULD));
+      scorers.get(Occur.SHOULD).clear();
       minShouldMatch = 0;
     }
     
-    if (required.isEmpty() && optional.isEmpty()) {
+    if (scorers.get(Occur.FILTER).isEmpty() && scorers.get(Occur.MUST).isEmpty() && scorers.get(Occur.SHOULD).isEmpty()) {
       // no required and optional clauses.
       return null;
-    } else if (optional.size() < minShouldMatch) {
+    } else if (scorers.get(Occur.SHOULD).size() < minShouldMatch) {
       // either >1 req scorer, or there are 0 req scorers and at least 1
       // optional scorer. Therefore if there are not enough optional scorers
       // no documents will be matched by the query
@@ -339,87 +341,11 @@ final class BooleanWeight extends Weight {
     }
 
     // we don't need scores, so if we have required clauses, drop optional clauses completely
-    if (!needsScores && minShouldMatch == 0 && required.size() > 0) {
-      optional.clear();
-    }
-    
-    // three cases: conjunction, disjunction, or mix
-    
-    // pure conjunction
-    if (optional.isEmpty()) {
-      return excl(req(required, requiredScoring), prohibited);
+    if (!needsScores && minShouldMatch == 0 && scorers.get(Occur.MUST).size() + scorers.get(Occur.FILTER).size() > 0) {
+      scorers.get(Occur.SHOULD).clear();
     }
-    
-    // pure disjunction
-    if (required.isEmpty()) {
-      return excl(opt(optional, minShouldMatch), prohibited);
-    }
-    
-    // conjunction-disjunction mix:
-    // we create the required and optional pieces, and then
-    // combine the two: if minNrShouldMatch > 0, then it's a conjunction: because the
-    // optional side must match. otherwise it's required + optional
-    
-    Scorer req = excl(req(required, requiredScoring), prohibited);
-    Scorer opt = opt(optional, minShouldMatch);
 
-    if (minShouldMatch > 0) {
-      return new ConjunctionScorer(this, Arrays.asList(req, opt), Arrays.asList(req, opt));
-    } else {
-      return new ReqOptSumScorer(req, opt);          
-    }
+    return new Boolean2ScorerSupplier(this, scorers, needsScores, minShouldMatch);
   }
 
-  /** Create a new scorer for the given required clauses. Note that
-   *  {@code requiredScoring} is a subset of {@code required} containing
-   *  required clauses that should participate in scoring. */
-  private Scorer req(List<Scorer> required, List<Scorer> requiredScoring) {
-    if (required.size() == 1) {
-      Scorer req = required.get(0);
-
-      if (needsScores == false) {
-        return req;
-      }
-
-      if (requiredScoring.isEmpty()) {
-        // Scores are needed but we only have a filter clause
-        // BooleanWeight expects that calling score() is ok so we need to wrap
-        // to prevent score() from being propagated
-        return new FilterScorer(req) {
-          @Override
-          public float score() throws IOException {
-            return 0f;
-          }
-          @Override
-          public int freq() throws IOException {
-            return 0;
-          }
-        };
-      }
-      
-      return req;
-    } else {
-      return new ConjunctionScorer(this, required, requiredScoring);
-    }
-  }
-  
-  private Scorer excl(Scorer main, List<Scorer> prohibited) throws IOException {
-    if (prohibited.isEmpty()) {
-      return main;
-    } else if (prohibited.size() == 1) {
-      return new ReqExclScorer(main, prohibited.get(0));
-    } else {
-      return new ReqExclScorer(main, new DisjunctionSumScorer(this, prohibited, false));
-    }
-  }
-  
-  private Scorer opt(List<Scorer> optional, int minShouldMatch) throws IOException {
-    if (optional.size() == 1) {
-      return optional.get(0);
-    } else if (minShouldMatch > 1) {
-      return new MinShouldMatchSumScorer(this, optional, minShouldMatch);
-    } else {
-      return new DisjunctionSumScorer(this, optional, needsScores);
-    }
-  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/search/ConjunctionDISI.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/ConjunctionDISI.java b/lucene/core/src/java/org/apache/lucene/search/ConjunctionDISI.java
index 43d03b2..780e854 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ConjunctionDISI.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ConjunctionDISI.java
@@ -41,7 +41,7 @@ public final class ConjunctionDISI extends DocIdSetIterator {
    * returned {@link DocIdSetIterator} might leverage two-phase iteration in
    * which case it is possible to retrieve the {@link TwoPhaseIterator} using
    * {@link TwoPhaseIterator#unwrap}. */
-  public static DocIdSetIterator intersectScorers(List<Scorer> scorers) {
+  public static DocIdSetIterator intersectScorers(Collection<Scorer> scorers) {
     if (scorers.size() < 2) {
       throw new IllegalArgumentException("Cannot make a ConjunctionDISI of less than 2 iterators");
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java b/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java
index 0066952..9cddab8 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java
@@ -20,7 +20,6 @@ package org.apache.lucene.search;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 
 /** Scorer for conjunctions, sets of queries, all of which are required. */
 class ConjunctionScorer extends Scorer {
@@ -29,7 +28,7 @@ class ConjunctionScorer extends Scorer {
   final Scorer[] scorers;
 
   /** Create a new {@link ConjunctionScorer}, note that {@code scorers} must be a subset of {@code required}. */
-  ConjunctionScorer(Weight weight, List<Scorer> required, List<Scorer> scorers) {
+  ConjunctionScorer(Weight weight, Collection<Scorer> required, Collection<Scorer> scorers) {
     super(weight);
     assert required.containsAll(scorers);
     this.disi = ConjunctionDISI.intersectScorers(required);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java b/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
index c5a7d08..dbd05e8 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
@@ -125,28 +125,48 @@ public final class ConstantScoreQuery extends Query {
         }
 
         @Override
-        public Scorer scorer(LeafReaderContext context) throws IOException {
-          final Scorer innerScorer = innerWeight.scorer(context);
-          if (innerScorer == null) {
+        public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
+          ScorerSupplier innerScorerSupplier = innerWeight.scorerSupplier(context);
+          if (innerScorerSupplier == null) {
             return null;
           }
-          final float score = score();
-          return new FilterScorer(innerScorer) {
+          return new ScorerSupplier() {
             @Override
-            public float score() throws IOException {
-              return score;
-            }
-            @Override
-            public int freq() throws IOException {
-              return 1;
+            public Scorer get(boolean randomAccess) throws IOException {
+              final Scorer innerScorer = innerScorerSupplier.get(randomAccess);
+              final float score = score();
+              return new FilterScorer(innerScorer) {
+                @Override
+                public float score() throws IOException {
+                  return score;
+                }
+                @Override
+                public int freq() throws IOException {
+                  return 1;
+                }
+                @Override
+                public Collection<ChildScorer> getChildren() {
+                  return Collections.singleton(new ChildScorer(innerScorer, "constant"));
+                }
+              };
             }
+
             @Override
-            public Collection<ChildScorer> getChildren() {
-              return Collections.singleton(new ChildScorer(innerScorer, "constant"));
+            public long cost() {
+              return innerScorerSupplier.cost();
             }
           };
         }
 
+        @Override
+        public Scorer scorer(LeafReaderContext context) throws IOException {
+          ScorerSupplier scorerSupplier = scorerSupplier(context);
+          if (scorerSupplier == null) {
+            return null;
+          }
+          return scorerSupplier.get(false);
+        }
+
       };
     } else {
       return innerWeight;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/search/MinShouldMatchSumScorer.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/MinShouldMatchSumScorer.java b/lucene/core/src/java/org/apache/lucene/search/MinShouldMatchSumScorer.java
index 032b5fe..c2c419c 100644
--- a/lucene/core/src/java/org/apache/lucene/search/MinShouldMatchSumScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/MinShouldMatchSumScorer.java
@@ -22,6 +22,8 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.LongStream;
+import java.util.stream.StreamSupport;
 
 import org.apache.lucene.util.PriorityQueue;
 
@@ -47,7 +49,7 @@ import static org.apache.lucene.search.DisiPriorityQueue.rightNode;
  */
 final class MinShouldMatchSumScorer extends Scorer {
 
-  private static long cost(Collection<Scorer> scorers, int minShouldMatch) {
+  static long cost(LongStream costs, int numScorers, int minShouldMatch) {
     // the idea here is the following: a boolean query c1,c2,...cn with minShouldMatch=m
     // could be rewritten to:
     // (c1 AND (c2..cn|msm=m-1)) OR (!c1 AND (c2..cn|msm=m))
@@ -61,20 +63,14 @@ final class MinShouldMatchSumScorer extends Scorer {
 
     // If we recurse infinitely, we find out that the cost of a msm query is the sum of the
     // costs of the num_scorers - minShouldMatch + 1 least costly scorers
-    final PriorityQueue<Scorer> pq = new PriorityQueue<Scorer>(scorers.size() - minShouldMatch + 1) {
+    final PriorityQueue<Long> pq = new PriorityQueue<Long>(numScorers - minShouldMatch + 1) {
       @Override
-      protected boolean lessThan(Scorer a, Scorer b) {
-        return a.iterator().cost() > b.iterator().cost();
+      protected boolean lessThan(Long a, Long b) {
+        return a > b;
       }
     };
-    for (Scorer scorer : scorers) {
-      pq.insertWithOverflow(scorer);
-    }
-    long cost = 0;
-    for (Scorer scorer = pq.pop(); scorer != null; scorer = pq.pop()) {
-      cost += scorer.iterator().cost();
-    }
-    return cost;
+    costs.forEach(pq::insertWithOverflow);
+    return StreamSupport.stream(pq.spliterator(), false).mapToLong(Number::longValue).sum();
   }
 
   final int minShouldMatch;
@@ -124,7 +120,7 @@ final class MinShouldMatchSumScorer extends Scorer {
       children.add(new ChildScorer(scorer, "SHOULD"));
     }
     this.childScorers = Collections.unmodifiableCollection(children);
-    this.cost = cost(scorers, minShouldMatch);
+    this.cost = cost(scorers.stream().map(Scorer::iterator).mapToLong(DocIdSetIterator::cost), scorers.size(), minShouldMatch);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java b/lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java
index 5fd0167..29c6e7f 100644
--- a/lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/PointRangeQuery.java
@@ -104,71 +104,67 @@ public abstract class PointRangeQuery extends Query {
 
     return new ConstantScoreWeight(this, boost) {
 
-      private DocIdSet buildMatchingDocIdSet(LeafReader reader, PointValues values) throws IOException {
-        DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
+      private IntersectVisitor getIntersectVisitor(DocIdSetBuilder result) {
+        return new IntersectVisitor() {
 
-        values.intersect(
-            new IntersectVisitor() {
+          DocIdSetBuilder.BulkAdder adder;
 
-              DocIdSetBuilder.BulkAdder adder;
+          @Override
+          public void grow(int count) {
+            adder = result.grow(count);
+          }
 
-              @Override
-              public void grow(int count) {
-                adder = result.grow(count);
-              }
+          @Override
+          public void visit(int docID) {
+            adder.add(docID);
+          }
 
-              @Override
-              public void visit(int docID) {
-                adder.add(docID);
+          @Override
+          public void visit(int docID, byte[] packedValue) {
+            for(int dim=0;dim<numDims;dim++) {
+              int offset = dim*bytesPerDim;
+              if (StringHelper.compare(bytesPerDim, packedValue, offset, lowerPoint, offset) < 0) {
+                // Doc's value is too low, in this dimension
+                return;
               }
-
-              @Override
-              public void visit(int docID, byte[] packedValue) {
-                for(int dim=0;dim<numDims;dim++) {
-                  int offset = dim*bytesPerDim;
-                  if (StringHelper.compare(bytesPerDim, packedValue, offset, lowerPoint, offset) < 0) {
-                    // Doc's value is too low, in this dimension
-                    return;
-                  }
-                  if (StringHelper.compare(bytesPerDim, packedValue, offset, upperPoint, offset) > 0) {
-                    // Doc's value is too high, in this dimension
-                    return;
-                  }
-                }
-
-                // Doc is in-bounds
-                adder.add(docID);
+              if (StringHelper.compare(bytesPerDim, packedValue, offset, upperPoint, offset) > 0) {
+                // Doc's value is too high, in this dimension
+                return;
               }
+            }
 
-              @Override
-              public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
-
-                boolean crosses = false;
+            // Doc is in-bounds
+            adder.add(docID);
+          }
 
-                for(int dim=0;dim<numDims;dim++) {
-                  int offset = dim*bytesPerDim;
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
 
-                  if (StringHelper.compare(bytesPerDim, minPackedValue, offset, upperPoint, offset) > 0 ||
-                      StringHelper.compare(bytesPerDim, maxPackedValue, offset, lowerPoint, offset) < 0) {
-                    return Relation.CELL_OUTSIDE_QUERY;
-                  }
+            boolean crosses = false;
 
-                  crosses |= StringHelper.compare(bytesPerDim, minPackedValue, offset, lowerPoint, offset) < 0 ||
-                    StringHelper.compare(bytesPerDim, maxPackedValue, offset, upperPoint, offset) > 0;
-                }
+            for(int dim=0;dim<numDims;dim++) {
+              int offset = dim*bytesPerDim;
 
-                if (crosses) {
-                  return Relation.CELL_CROSSES_QUERY;
-                } else {
-                  return Relation.CELL_INSIDE_QUERY;
-                }
+              if (StringHelper.compare(bytesPerDim, minPackedValue, offset, upperPoint, offset) > 0 ||
+                  StringHelper.compare(bytesPerDim, maxPackedValue, offset, lowerPoint, offset) < 0) {
+                return Relation.CELL_OUTSIDE_QUERY;
               }
-            });
-        return result.build();
+
+              crosses |= StringHelper.compare(bytesPerDim, minPackedValue, offset, lowerPoint, offset) < 0 ||
+                  StringHelper.compare(bytesPerDim, maxPackedValue, offset, upperPoint, offset) > 0;
+            }
+
+            if (crosses) {
+              return Relation.CELL_CROSSES_QUERY;
+            } else {
+              return Relation.CELL_INSIDE_QUERY;
+            }
+          }
+        };
       }
 
       @Override
-      public Scorer scorer(LeafReaderContext context) throws IOException {
+      public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
         LeafReader reader = context.reader();
 
         PointValues values = reader.getPointValues(field);
@@ -201,15 +197,55 @@ public abstract class PointRangeQuery extends Query {
           allDocsMatch = false;
         }
 
-        DocIdSetIterator iterator;
+        final Weight weight = this;
         if (allDocsMatch) {
           // all docs have a value and all points are within bounds, so everything matches
-          iterator = DocIdSetIterator.all(reader.maxDoc());
+          return new ScorerSupplier() {
+            @Override
+            public Scorer get(boolean randomAccess) {
+              return new ConstantScoreScorer(weight, score(),
+                  DocIdSetIterator.all(reader.maxDoc()));
+            }
+            
+            @Override
+            public long cost() {
+              return reader.maxDoc();
+            }
+          };
         } else {
-          iterator = buildMatchingDocIdSet(reader, values).iterator();
+          return new ScorerSupplier() {
+
+            final DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
+            final IntersectVisitor visitor = getIntersectVisitor(result);
+            long cost = -1;
+
+            @Override
+            public Scorer get(boolean randomAccess) throws IOException {
+              values.intersect(visitor);
+              DocIdSetIterator iterator = result.build().iterator();
+              return new ConstantScoreScorer(weight, score(), iterator);
+            }
+            
+            @Override
+            public long cost() {
+              if (cost == -1) {
+                // Computing the cost may be expensive, so only do it if necessary
+                cost = values.estimatePointCount(visitor);
+                assert cost >= 0;
+              }
+              return cost;
+            }
+          };
         }
+      }
 
-        return new ConstantScoreScorer(this, score(), iterator);
+      @Override
+      public Scorer scorer(LeafReaderContext context) throws IOException {
+        ScorerSupplier scorerSupplier = scorerSupplier(context);
+        if (scorerSupplier == null) {
+          return null;
+        }
+        return scorerSupplier.get(false);
       }
     };
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/search/ScorerSupplier.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/ScorerSupplier.java b/lucene/core/src/java/org/apache/lucene/search/ScorerSupplier.java
new file mode 100644
index 0000000..3f6906a
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/search/ScorerSupplier.java
@@ -0,0 +1,47 @@
+/*
+ * 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.lucene.search;
+
+import java.io.IOException;
+
+/**
+ * A supplier of {@link Scorer}. This allows to get an estimate of the cost before
+ * building the {@link Scorer}.
+ */
+public abstract class ScorerSupplier {
+
+  /**
+   * Get the {@link Scorer}. This may not return {@code null} and must be called
+   * at most once.
+   * @param randomAccess A hint about the expected usage of the {@link Scorer}.
+   * If {@link DocIdSetIterator#advance} or {@link TwoPhaseIterator} will be
+   * used to check whether given doc ids match, then pass {@code true}.
+   * Otherwise if the {@link Scorer} will be mostly used to lead the iteration
+   * using {@link DocIdSetIterator#nextDoc()}, then {@code false} should be
+   * passed. Under doubt, pass {@code false} which usually has a better
+   * worst-case.
+   */
+  public abstract Scorer get(boolean randomAccess) throws IOException;
+
+  /**
+   * Get an estimate of the {@link Scorer} that would be returned by {@link #get}.
+   * This may be a costly operation, so it should only be called if necessary.
+   * @see DocIdSetIterator#cost
+   */
+  public abstract long cost();
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/search/Weight.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/Weight.java b/lucene/core/src/java/org/apache/lucene/search/Weight.java
index 47f553e..eef052d 100644
--- a/lucene/core/src/java/org/apache/lucene/search/Weight.java
+++ b/lucene/core/src/java/org/apache/lucene/search/Weight.java
@@ -103,6 +103,31 @@ public abstract class Weight {
   public abstract Scorer scorer(LeafReaderContext context) throws IOException;
 
   /**
+   * Optional method.
+   * Get a {@link ScorerSupplier}, which allows to know the cost of the {@link Scorer}
+   * before building it. The default implementation calls {@link #scorer} and
+   * builds a {@link ScorerSupplier} wrapper around it.
+   * @see #scorer
+   */
+  public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
+    final Scorer scorer = scorer(context);
+    if (scorer == null) {
+      return null;
+    }
+    return new ScorerSupplier() {
+      @Override
+      public Scorer get(boolean randomAccess) {
+        return scorer;
+      }
+
+      @Override
+      public long cost() {
+        return scorer.iterator().cost();
+      }
+    };
+  }
+
+  /**
    * Optional method, to return a {@link BulkScorer} to
    * score the query and send hits to a {@link Collector}.
    * Only queries that have a different top-level approach

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
index 44744c1..14e1adb 100644
--- a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
+++ b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDReader.java
@@ -482,10 +482,16 @@ public final class BKDReader extends PointValues implements Accountable {
     }
   }
 
+  @Override
   public void intersect(IntersectVisitor visitor) throws IOException {
     intersect(getIntersectState(visitor), minPackedValue, maxPackedValue);
   }
 
+  @Override
+  public long estimatePointCount(IntersectVisitor visitor) {
+    return estimatePointCount(getIntersectState(visitor), minPackedValue, maxPackedValue);
+  }
+
   /** Fast path: this is called when the query box fully encompasses all cells under this node. */
   private void addAll(IntersectState state) throws IOException {
     //System.out.println("R: addAll nodeID=" + nodeID);
@@ -696,6 +702,59 @@ public final class BKDReader extends PointValues implements Accountable {
     }
   }
 
+  private long estimatePointCount(IntersectState state, byte[] cellMinPacked, byte[] cellMaxPacked) {
+
+    /*
+    System.out.println("\nR: intersect nodeID=" + state.index.getNodeID());
+    for(int dim=0;dim<numDims;dim++) {
+      System.out.println("  dim=" + dim + "\n    cellMin=" + new BytesRef(cellMinPacked, dim*bytesPerDim, bytesPerDim) + "\n    cellMax=" + new BytesRef(cellMaxPacked, dim*bytesPerDim, bytesPerDim));
+    }
+    */
+
+    Relation r = state.visitor.compare(cellMinPacked, cellMaxPacked);
+
+    if (r == Relation.CELL_OUTSIDE_QUERY) {
+      // This cell is fully outside of the query shape: stop recursing
+      return 0L;
+    } else if (state.index.isLeafNode()) {
+      // Assume all points match and there are no dups
+      return maxPointsInLeafNode;
+    } else {
+      
+      // Non-leaf node: recurse on the split left and right nodes
+      int splitDim = state.index.getSplitDim();
+      assert splitDim >= 0: "splitDim=" + splitDim;
+      assert splitDim < numDims;
+
+      byte[] splitPackedValue = state.index.getSplitPackedValue();
+      BytesRef splitDimValue = state.index.getSplitDimValue();
+      assert splitDimValue.length == bytesPerDim;
+      //System.out.println("  splitDimValue=" + splitDimValue + " splitDim=" + splitDim);
+
+      // make sure cellMin <= splitValue <= cellMax:
+      assert StringHelper.compare(bytesPerDim, cellMinPacked, splitDim*bytesPerDim, splitDimValue.bytes, splitDimValue.offset) <= 0: "bytesPerDim=" + bytesPerDim + " splitDim=" + splitDim + " numDims=" + numDims;
+      assert StringHelper.compare(bytesPerDim, cellMaxPacked, splitDim*bytesPerDim, splitDimValue.bytes, splitDimValue.offset) >= 0: "bytesPerDim=" + bytesPerDim + " splitDim=" + splitDim + " numDims=" + numDims;
+
+      // Recurse on left sub-tree:
+      System.arraycopy(cellMaxPacked, 0, splitPackedValue, 0, packedBytesLength);
+      System.arraycopy(splitDimValue.bytes, splitDimValue.offset, splitPackedValue, splitDim*bytesPerDim, bytesPerDim);
+      state.index.pushLeft();
+      final long leftCost = estimatePointCount(state, cellMinPacked, splitPackedValue);
+      state.index.pop();
+
+      // Restore the split dim value since it may have been overwritten while recursing:
+      System.arraycopy(splitPackedValue, splitDim*bytesPerDim, splitDimValue.bytes, splitDimValue.offset, bytesPerDim);
+
+      // Recurse on right sub-tree:
+      System.arraycopy(cellMinPacked, 0, splitPackedValue, 0, packedBytesLength);
+      System.arraycopy(splitDimValue.bytes, splitDimValue.offset, splitPackedValue, splitDim*bytesPerDim, bytesPerDim);
+      state.index.pushRight();
+      final long rightCost = estimatePointCount(state, splitPackedValue, cellMaxPacked);
+      state.index.pop();
+      return leftCost + rightCost;
+    }
+  }
+
   @Override
   public long ramBytesUsed() {
     if (packedIndex != null) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/test/org/apache/lucene/search/TestBoolean2ScorerSupplier.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestBoolean2ScorerSupplier.java b/lucene/core/src/test/org/apache/lucene/search/TestBoolean2ScorerSupplier.java
new file mode 100644
index 0000000..7f46a22
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/search/TestBoolean2ScorerSupplier.java
@@ -0,0 +1,332 @@
+/*
+ * 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.lucene.search;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.Map;
+
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+
+import com.carrotsearch.randomizedtesting.generators.RandomPicks;
+
+public class TestBoolean2ScorerSupplier extends LuceneTestCase {
+
+  private static class FakeScorer extends Scorer {
+
+    private final DocIdSetIterator it;
+
+    FakeScorer(long cost) {
+      super(null);
+      this.it = DocIdSetIterator.all(Math.toIntExact(cost));
+    }
+
+    @Override
+    public int docID() {
+      return it.docID();
+    }
+
+    @Override
+    public float score() throws IOException {
+      return 1;
+    }
+
+    @Override
+    public int freq() throws IOException {
+      return 1;
+    }
+
+    @Override
+    public DocIdSetIterator iterator() {
+      return it;
+    }
+
+    @Override
+    public String toString() {
+      return "FakeScorer(cost=" + it.cost() + ")";
+    }
+
+  }
+
+  private static class FakeScorerSupplier extends ScorerSupplier {
+
+    private final long cost;
+    private final Boolean randomAccess;
+
+    FakeScorerSupplier(long cost) {
+      this.cost = cost;
+      this.randomAccess = null;
+    }
+
+    FakeScorerSupplier(long cost, boolean randomAccess) {
+      this.cost = cost;
+      this.randomAccess = randomAccess;
+    }
+
+    @Override
+    public Scorer get(boolean randomAccess) throws IOException {
+      if (this.randomAccess != null) {
+        assertEquals(this.toString(), this.randomAccess, randomAccess);
+      }
+      return new FakeScorer(cost);
+    }
+
+    @Override
+    public long cost() {
+      return cost;
+    }
+    
+    @Override
+    public String toString() {
+      return "FakeLazyScorer(cost=" + cost + ",randomAccess=" + randomAccess + ")";
+    }
+
+  }
+
+  public void testConjunctionCost() {
+    Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+    for (Occur occur : Occur.values()) {
+      subs.put(occur, new ArrayList<>());
+    }
+
+    subs.get(RandomPicks.randomFrom(random(), Arrays.asList(Occur.FILTER, Occur.MUST))).add(new FakeScorerSupplier(42));
+    assertEquals(42, new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 0).cost());
+
+    subs.get(RandomPicks.randomFrom(random(), Arrays.asList(Occur.FILTER, Occur.MUST))).add(new FakeScorerSupplier(12));
+    assertEquals(12, new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 0).cost());
+
+    subs.get(RandomPicks.randomFrom(random(), Arrays.asList(Occur.FILTER, Occur.MUST))).add(new FakeScorerSupplier(20));
+    assertEquals(12, new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 0).cost());
+  }
+
+  public void testDisjunctionCost() throws IOException {
+    Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+    for (Occur occur : Occur.values()) {
+      subs.put(occur, new ArrayList<>());
+    }
+
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(42));
+    ScorerSupplier s = new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 0);
+    assertEquals(42, s.cost());
+    assertEquals(42, s.get(random().nextBoolean()).iterator().cost());
+
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(12));
+    s = new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 0);
+    assertEquals(42 + 12, s.cost());
+    assertEquals(42 + 12, s.get(random().nextBoolean()).iterator().cost());
+
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(20));
+    s = new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 0);
+    assertEquals(42 + 12 + 20, s.cost());
+    assertEquals(42 + 12 + 20, s.get(random().nextBoolean()).iterator().cost());
+  }
+
+  public void testDisjunctionWithMinShouldMatchCost() throws IOException {
+    Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+    for (Occur occur : Occur.values()) {
+      subs.put(occur, new ArrayList<>());
+    }
+
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(42));
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(12));
+    ScorerSupplier s = new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 1);
+    assertEquals(42 + 12, s.cost());
+    assertEquals(42 + 12, s.get(random().nextBoolean()).iterator().cost());
+
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(20));
+    s = new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 1);
+    assertEquals(42 + 12 + 20, s.cost());
+    assertEquals(42 + 12 + 20, s.get(random().nextBoolean()).iterator().cost());
+    s = new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 2);
+    assertEquals(12 + 20, s.cost());
+    assertEquals(12 + 20, s.get(random().nextBoolean()).iterator().cost());
+
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(30));
+    s = new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 1);
+    assertEquals(42 + 12 + 20 + 30, s.cost());
+    assertEquals(42 + 12 + 20 + 30, s.get(random().nextBoolean()).iterator().cost());
+    s = new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 2);
+    assertEquals(12 + 20 + 30, s.cost());
+    assertEquals(12 + 20 + 30, s.get(random().nextBoolean()).iterator().cost());
+    s = new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 3);
+    assertEquals(12 + 20, s.cost());
+    assertEquals(12 + 20, s.get(random().nextBoolean()).iterator().cost());
+  }
+
+  public void testDuelCost() throws Exception {
+    final int iters = atLeast(1000);
+    for (int iter = 0; iter < iters; ++iter) {
+      Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+      for (Occur occur : Occur.values()) {
+        subs.put(occur, new ArrayList<>());
+      }
+      int numClauses = TestUtil.nextInt(random(), 1, 10);
+      int numShoulds = 0;
+      int numRequired = 0;
+      for (int j = 0; j < numClauses; ++j) {
+        Occur occur = RandomPicks.randomFrom(random(), Occur.values());
+        subs.get(occur).add(new FakeScorerSupplier(random().nextInt(100)));
+        if (occur == Occur.SHOULD) {
+          ++numShoulds;
+        } else if (occur == Occur.FILTER || occur == Occur.MUST) {
+          numRequired++;
+        }
+      }
+      boolean needsScores = random().nextBoolean();
+      if (needsScores == false && numRequired > 0) {
+        numClauses -= numShoulds;
+        numShoulds = 0;
+        subs.get(Occur.SHOULD).clear();
+      }
+      if (numShoulds + numRequired == 0) {
+        // only negative clauses, invalid
+        continue;
+      }
+      int minShouldMatch = numShoulds == 0 ? 0 : TestUtil.nextInt(random(), 0, numShoulds - 1);
+      Boolean2ScorerSupplier supplier = new Boolean2ScorerSupplier(null,
+          subs, needsScores, minShouldMatch);
+      long cost1 = supplier.cost();
+      long cost2 = supplier.get(false).iterator().cost();
+      assertEquals("clauses=" + subs + ", minShouldMatch=" + minShouldMatch, cost1, cost2);
+    }
+  }
+
+  // test the tester...
+  public void testFakeScorerSupplier() {
+    FakeScorerSupplier randomAccessSupplier = new FakeScorerSupplier(random().nextInt(100), true);
+    expectThrows(AssertionError.class, () -> randomAccessSupplier.get(false));
+    FakeScorerSupplier sequentialSupplier = new FakeScorerSupplier(random().nextInt(100), false);
+    expectThrows(AssertionError.class, () -> sequentialSupplier.get(true));
+  }
+
+  public void testConjunctionRandomAccess() throws IOException {
+    Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+    for (Occur occur : Occur.values()) {
+      subs.put(occur, new ArrayList<>());
+    }
+
+    // If sequential access is required, only the least costly clause does not use random-access
+    subs.get(RandomPicks.randomFrom(random(), Arrays.asList(Occur.FILTER, Occur.MUST))).add(new FakeScorerSupplier(42, true));
+    subs.get(RandomPicks.randomFrom(random(), Arrays.asList(Occur.FILTER, Occur.MUST))).add(new FakeScorerSupplier(12, false));
+    new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 0).get(false); // triggers assertions as a side-effect
+
+    subs = new EnumMap<>(Occur.class);
+    for (Occur occur : Occur.values()) {
+      subs.put(occur, new ArrayList<>());
+    }
+
+    // If random access is required, then we propagate to sub clauses
+    subs.get(RandomPicks.randomFrom(random(), Arrays.asList(Occur.FILTER, Occur.MUST))).add(new FakeScorerSupplier(42, true));
+    subs.get(RandomPicks.randomFrom(random(), Arrays.asList(Occur.FILTER, Occur.MUST))).add(new FakeScorerSupplier(12, true));
+    new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 0).get(true); // triggers assertions as a side-effect
+  }
+
+  public void testDisjunctionRandomAccess() throws IOException {
+    // disjunctions propagate
+    for (boolean randomAccess : new boolean[] {false, true}) {
+      Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+      for (Occur occur : Occur.values()) {
+        subs.put(occur, new ArrayList<>());
+      }
+      subs.get(Occur.SHOULD).add(new FakeScorerSupplier(42, randomAccess));
+      subs.get(Occur.SHOULD).add(new FakeScorerSupplier(12, randomAccess));
+      new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 0).get(randomAccess); // triggers assertions as a side-effect
+    }
+  }
+
+  public void testDisjunctionWithMinShouldMatchRandomAccess() throws IOException {
+    Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+    for (Occur occur : Occur.values()) {
+      subs.put(occur, new ArrayList<>());
+    }
+
+    // Only the most costly clause uses random-access in that case:
+    // most of time, we will find agreement between the 2 least costly
+    // clauses and only then check whether the 3rd one matches too
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(42, true));
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(12, false));
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(30, false));
+    new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 2).get(false); // triggers assertions as a side-effect
+
+    subs = new EnumMap<>(Occur.class);
+    for (Occur occur : Occur.values()) {
+      subs.put(occur, new ArrayList<>());
+    }
+
+    // When random-access is true, just propagate
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(42, true));
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(12, true));
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(30, true));
+    new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 2).get(true); // triggers assertions as a side-effect
+
+    subs = new EnumMap<>(Occur.class);
+    for (Occur occur : Occur.values()) {
+      subs.put(occur, new ArrayList<>());
+    }
+
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(42, true));
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(12, false));
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(30, false));
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(20, false));
+    new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 2).get(false); // triggers assertions as a side-effect
+
+    subs = new EnumMap<>(Occur.class);
+    for (Occur occur : Occur.values()) {
+      subs.put(occur, new ArrayList<>());
+    }
+
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(42, true));
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(12, false));
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(30, true));
+    subs.get(Occur.SHOULD).add(new FakeScorerSupplier(20, false));
+    new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 3).get(false); // triggers assertions as a side-effect
+  }
+
+  public void testProhibitedRandomAccess() throws IOException {
+    for (boolean randomAccess : new boolean[] {false, true}) {
+      Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+      for (Occur occur : Occur.values()) {
+        subs.put(occur, new ArrayList<>());
+      }
+
+      // The MUST_NOT clause always uses random-access
+      subs.get(Occur.MUST).add(new FakeScorerSupplier(42, randomAccess));
+      subs.get(Occur.MUST_NOT).add(new FakeScorerSupplier(TestUtil.nextInt(random(), 1, 100), true));
+      new Boolean2ScorerSupplier(null, subs, random().nextBoolean(), 0).get(randomAccess); // triggers assertions as a side-effect
+    }
+  }
+
+  public void testMixedRandomAccess() throws IOException {
+    for (boolean randomAccess : new boolean[] {false, true}) {
+      Map<Occur, Collection<ScorerSupplier>> subs = new EnumMap<>(Occur.class);
+      for (Occur occur : Occur.values()) {
+        subs.put(occur, new ArrayList<>());
+      }
+
+      // The SHOULD clause always uses random-access if there is a MUST clause
+      subs.get(Occur.MUST).add(new FakeScorerSupplier(42, randomAccess));
+      subs.get(Occur.SHOULD).add(new FakeScorerSupplier(TestUtil.nextInt(random(), 1, 100), true));
+      new Boolean2ScorerSupplier(null, subs, true, 0).get(randomAccess); // triggers assertions as a side-effect
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/test/org/apache/lucene/search/TestBooleanQueryVisitSubscorers.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestBooleanQueryVisitSubscorers.java b/lucene/core/src/test/org/apache/lucene/search/TestBooleanQueryVisitSubscorers.java
index 60ba528..38ddcab 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestBooleanQueryVisitSubscorers.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestBooleanQueryVisitSubscorers.java
@@ -206,8 +206,8 @@ public class TestBooleanQueryVisitSubscorers extends LuceneTestCase {
           "    MUST ConstantScoreScorer\n" +
           "    MUST MinShouldMatchSumScorer\n" +
           "            SHOULD TermScorer body:nutch\n" +
-          "            SHOULD TermScorer body:web\n" +
-          "            SHOULD TermScorer body:crawler",
+          "            SHOULD TermScorer body:crawler\n" +
+          "            SHOULD TermScorer body:web",
           summary);
     }
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/test/org/apache/lucene/search/TestFilterWeight.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestFilterWeight.java b/lucene/core/src/test/org/apache/lucene/search/TestFilterWeight.java
index cfa01bf..b58fe1b 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestFilterWeight.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestFilterWeight.java
@@ -18,6 +18,7 @@ package org.apache.lucene.search;
 
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.util.Arrays;
 
 import org.apache.lucene.util.LuceneTestCase;
 import org.junit.Test;
@@ -35,7 +36,7 @@ public class TestFilterWeight extends LuceneTestCase {
       final int modifiers = superClassMethod.getModifiers();
       if (Modifier.isFinal(modifiers)) continue;
       if (Modifier.isStatic(modifiers)) continue;
-      if (superClassMethod.getName().equals("bulkScorer")) {
+      if (Arrays.asList("bulkScorer", "scorerSupplier").contains(superClassMethod.getName())) {
         try {
           final Method subClassMethod = subClass.getDeclaredMethod(
               superClassMethod.getName(),

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/test/org/apache/lucene/util/TestDocIdSetBuilder.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/util/TestDocIdSetBuilder.java b/lucene/core/src/test/org/apache/lucene/util/TestDocIdSetBuilder.java
index 625b8c2..f87a73a 100644
--- a/lucene/core/src/test/org/apache/lucene/util/TestDocIdSetBuilder.java
+++ b/lucene/core/src/test/org/apache/lucene/util/TestDocIdSetBuilder.java
@@ -312,6 +312,11 @@ public class TestDocIdSetBuilder extends LuceneTestCase {
     }
 
     @Override
+    public long estimatePointCount(IntersectVisitor visitor) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
     public byte[] getMinPackedValue() throws IOException {
       throw new UnsupportedOperationException();
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/core/src/test/org/apache/lucene/util/bkd/TestMutablePointsReaderUtils.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/util/bkd/TestMutablePointsReaderUtils.java b/lucene/core/src/test/org/apache/lucene/util/bkd/TestMutablePointsReaderUtils.java
index 8d2ea3e..62ab2b8 100644
--- a/lucene/core/src/test/org/apache/lucene/util/bkd/TestMutablePointsReaderUtils.java
+++ b/lucene/core/src/test/org/apache/lucene/util/bkd/TestMutablePointsReaderUtils.java
@@ -221,6 +221,11 @@ public class TestMutablePointsReaderUtils extends LuceneTestCase {
     }
 
     @Override
+    public long estimatePointCount(IntersectVisitor visitor) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
     public byte[] getMinPackedValue() throws IOException {
       throw new UnsupportedOperationException();
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java
----------------------------------------------------------------------
diff --git a/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java b/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java
index 218d26c..b1adf60 100644
--- a/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java
+++ b/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java
@@ -1522,6 +1522,11 @@ public class MemoryIndex {
       }
 
       @Override
+      public long estimatePointCount(IntersectVisitor visitor) {
+        return 1L;
+      }
+
+      @Override
       public byte[] getMinPackedValue() throws IOException {
         BytesRef[] values = info.pointValues;
         if (values != null) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/sandbox/src/java/org/apache/lucene/search/DocValuesRangeQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/DocValuesRangeQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/DocValuesRangeQuery.java
index 459ffa4..3d4feb9 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/search/DocValuesRangeQuery.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/search/DocValuesRangeQuery.java
@@ -23,8 +23,10 @@ import org.apache.lucene.index.DocValues;
 import org.apache.lucene.index.DocValuesType;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.PointValues;
 import org.apache.lucene.index.SortedNumericDocValues;
 import org.apache.lucene.index.SortedSetDocValues;
+import org.apache.lucene.index.Terms;
 import org.apache.lucene.util.BytesRef;
 
 /**
@@ -33,10 +35,11 @@ import org.apache.lucene.util.BytesRef;
  * dense case where most documents match this query, it <b>might</b> be as
  * fast or faster than a regular {@link PointRangeQuery}.
  *
- * <p>
- * <b>NOTE</b>: be very careful using this query: it is
- * typically much slower than using {@code TermsQuery},
- * but in certain specialized cases may be faster.
+ * <b>NOTE:</b> This query is typically best used within a
+ * {@link IndexOrDocValuesQuery} alongside a query that uses an indexed
+ * structure such as {@link PointValues points} or {@link Terms terms},
+ * which allows to run the query on doc values when that would be more
+ * efficient, and using an index otherwise.
  *
  * @lucene.experimental
  */

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/sandbox/src/java/org/apache/lucene/search/IndexOrDocValuesQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/IndexOrDocValuesQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/IndexOrDocValuesQuery.java
new file mode 100644
index 0000000..0f9e8e3
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/search/IndexOrDocValuesQuery.java
@@ -0,0 +1,116 @@
+/*
+ * 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.lucene.search;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.LeafReaderContext;
+
+/**
+ * A query that uses either an index (points or terms) or doc values in order
+ * to run a range query, depending which one is more efficient.
+ */
+public final class IndexOrDocValuesQuery extends Query {
+
+  private final Query indexQuery, dvQuery;
+
+  /**
+   * Constructor that takes both a query that executes on an index structure
+   * like the inverted index or the points tree, and another query that
+   * executes on doc values. Both queries must match the same documents and
+   * attribute constant scores.
+   */
+  public IndexOrDocValuesQuery(Query indexQuery, Query dvQuery) {
+    this.indexQuery = indexQuery;
+    this.dvQuery = dvQuery;
+  }
+
+  @Override
+  public String toString(String field) {
+    return indexQuery.toString(field);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (sameClassAs(obj) == false) {
+      return false;
+    }
+    IndexOrDocValuesQuery that = (IndexOrDocValuesQuery) obj;
+    return indexQuery.equals(that.indexQuery) && dvQuery.equals(that.dvQuery);
+  }
+
+  @Override
+  public int hashCode() {
+    int h = classHash();
+    h = 31 * h + indexQuery.hashCode();
+    h = 31 * h + dvQuery.hashCode();
+    return h;
+  }
+
+  @Override
+  public Query rewrite(IndexReader reader) throws IOException {
+    Query indexRewrite = indexQuery.rewrite(reader);
+    Query dvRewrite = dvQuery.rewrite(reader);
+    if (indexQuery != indexRewrite || dvQuery != dvRewrite) {
+      return new IndexOrDocValuesQuery(indexRewrite, dvRewrite);
+    }
+    return this;
+  }
+
+  @Override
+  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
+    final Weight indexWeight = indexQuery.createWeight(searcher, needsScores, boost);
+    final Weight dvWeight = dvQuery.createWeight(searcher, needsScores, boost);
+    return new ConstantScoreWeight(this, boost) {
+      @Override
+      public BulkScorer bulkScorer(LeafReaderContext context) throws IOException {
+        return indexWeight.bulkScorer(context);
+      }
+
+      @Override
+      public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
+        final ScorerSupplier indexScorerSupplier = indexWeight.scorerSupplier(context);
+        final ScorerSupplier dvScorerSupplier = dvWeight.scorerSupplier(context); 
+        if (indexScorerSupplier == null || dvScorerSupplier == null) {
+          return null;
+        }
+        return new ScorerSupplier() {
+          @Override
+          public Scorer get(boolean randomAccess) throws IOException {
+            return (randomAccess ? dvScorerSupplier : indexScorerSupplier).get(randomAccess);
+          }
+
+          @Override
+          public long cost() {
+            return Math.min(indexScorerSupplier.cost(), dvScorerSupplier.cost());
+          }
+        };
+      }
+
+      @Override
+      public Scorer scorer(LeafReaderContext context) throws IOException {
+        ScorerSupplier scorerSupplier = scorerSupplier(context);
+        if (scorerSupplier == null) {
+          return null;
+        }
+        return scorerSupplier.get(false);
+      }
+    };
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/sandbox/src/test/org/apache/lucene/search/TestIndexOrDocValuesQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestIndexOrDocValuesQuery.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestIndexOrDocValuesQuery.java
new file mode 100644
index 0000000..2a16e5d
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestIndexOrDocValuesQuery.java
@@ -0,0 +1,89 @@
+/*
+ * 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.lucene.search;
+
+import java.io.IOException;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.document.LongPoint;
+import org.apache.lucene.document.NumericDocValuesField;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+
+public class TestIndexOrDocValuesQuery extends LuceneTestCase {
+
+  public void testUseIndexForSelectiveQueries() throws IOException {
+    Directory dir = newDirectory();
+    IndexWriter w = new IndexWriter(dir, newIndexWriterConfig()
+        // relies on costs and PointValues.estimateCost so we need the default codec
+        .setCodec(TestUtil.getDefaultCodec()));
+    for (int i = 0; i < 2000; ++i) {
+      Document doc = new Document();
+      if (i == 42) {
+        doc.add(new StringField("f1", "bar", Store.NO));
+        doc.add(new LongPoint("f2", 42L));
+        doc.add(new NumericDocValuesField("f2", 42L));
+      } else if (i == 100) {
+        doc.add(new StringField("f1", "foo", Store.NO));
+        doc.add(new LongPoint("f2", 2L));
+        doc.add(new NumericDocValuesField("f2", 2L));
+      } else {
+        doc.add(new StringField("f1", "bar", Store.NO));
+        doc.add(new LongPoint("f2", 2L));
+        doc.add(new NumericDocValuesField("f2", 2L));
+      }
+      w.addDocument(doc);
+    }
+    w.forceMerge(1);
+    IndexReader reader = DirectoryReader.open(w);
+    IndexSearcher searcher = newSearcher(reader);
+    searcher.setQueryCache(null);
+
+    // The term query is more selective, so the IndexOrDocValuesQuery should use doc values
+    final Query q1 = new BooleanQuery.Builder()
+        .add(new TermQuery(new Term("f1", "foo")), Occur.MUST)
+        .add(new IndexOrDocValuesQuery(LongPoint.newExactQuery("f2", 2), new DocValuesNumbersQuery("f2", 2L)), Occur.MUST)
+        .build();
+
+    final Weight w1 = searcher.createNormalizedWeight(q1, random().nextBoolean());
+    final Scorer s1 = w1.scorer(reader.leaves().get(0));
+    assertNotNull(s1.twoPhaseIterator()); // means we use doc values
+
+    // The term query is less selective, so the IndexOrDocValuesQuery should use points
+    final Query q2 = new BooleanQuery.Builder()
+        .add(new TermQuery(new Term("f1", "bar")), Occur.MUST)
+        .add(new IndexOrDocValuesQuery(LongPoint.newExactQuery("f2", 42), new DocValuesNumbersQuery("f2", 42L)), Occur.MUST)
+        .build();
+
+    final Weight w2 = searcher.createNormalizedWeight(q2, random().nextBoolean());
+    final Scorer s2 = w2.scorer(reader.leaves().get(0));
+    assertNull(s2.twoPhaseIterator()); // means we use points
+
+    reader.close();
+    w.close();
+    dir.close();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/test-framework/src/java/org/apache/lucene/codecs/cranky/CrankyPointsFormat.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/codecs/cranky/CrankyPointsFormat.java b/lucene/test-framework/src/java/org/apache/lucene/codecs/cranky/CrankyPointsFormat.java
index ec7d75a..486d81c 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/codecs/cranky/CrankyPointsFormat.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/codecs/cranky/CrankyPointsFormat.java
@@ -134,6 +134,11 @@ class CrankyPointsFormat extends PointsFormat {
         }
 
         @Override
+        public long estimatePointCount(IntersectVisitor visitor) {
+          return delegate.estimatePointCount(visitor);
+        }
+
+        @Override
         public byte[] getMinPackedValue() throws IOException {
           if (random.nextInt(100) == 0) {
             throw new IOException("Fake IOException");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/test-framework/src/java/org/apache/lucene/index/AssertingLeafReader.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/index/AssertingLeafReader.java b/lucene/test-framework/src/java/org/apache/lucene/index/AssertingLeafReader.java
index 37c549e..e837359 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/index/AssertingLeafReader.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/index/AssertingLeafReader.java
@@ -884,6 +884,13 @@ public class AssertingLeafReader extends FilterLeafReader {
     }
 
     @Override
+    public long estimatePointCount(IntersectVisitor visitor) {
+      long cost = in.estimatePointCount(visitor);
+      assert cost >= 0;
+      return cost;
+    }
+
+    @Override
     public byte[] getMinPackedValue() throws IOException {
       return Objects.requireNonNull(in.getMinPackedValue());
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/86233cb9/lucene/test-framework/src/java/org/apache/lucene/search/AssertingWeight.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/search/AssertingWeight.java b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingWeight.java
index 75529df..7b6727d 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/search/AssertingWeight.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingWeight.java
@@ -33,9 +33,45 @@ class AssertingWeight extends FilterWeight {
 
   @Override
   public Scorer scorer(LeafReaderContext context) throws IOException {
-    final Scorer inScorer = in.scorer(context);
-    assert inScorer == null || inScorer.docID() == -1;
-    return AssertingScorer.wrap(new Random(random.nextLong()), inScorer, needsScores);
+    if (random.nextBoolean()) {
+      final Scorer inScorer = in.scorer(context);
+      assert inScorer == null || inScorer.docID() == -1;
+      return AssertingScorer.wrap(new Random(random.nextLong()), inScorer, needsScores);
+    } else {
+      final ScorerSupplier scorerSupplier = scorerSupplier(context);
+      if (scorerSupplier == null) {
+        return null;
+      }
+      if (random.nextBoolean()) {
+        // Evil: make sure computing the cost has no side effects
+        scorerSupplier.cost();
+      }
+      return scorerSupplier.get(false);
+    }
+  }
+
+  @Override
+  public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
+    final ScorerSupplier inScorerSupplier = in.scorerSupplier(context);
+    if (inScorerSupplier == null) {
+      return null;
+    }
+    return new ScorerSupplier() {
+      private boolean getCalled = false;
+      @Override
+      public Scorer get(boolean randomAccess) throws IOException {
+        assert getCalled == false;
+        getCalled = true;
+        return AssertingScorer.wrap(new Random(random.nextLong()), inScorerSupplier.get(randomAccess), needsScores);
+      }
+
+      @Override
+      public long cost() {
+        final long cost = inScorerSupplier.cost();
+        assert cost >= 0;
+        return cost;
+      }
+    };
   }
 
   @Override


[32/38] lucene-solr:jira/solr-9857: SOLR-9984: Deprecate GenericHadoopAuthPlugin in favor of HadoopAuthPlugin

Posted by ab...@apache.org.
SOLR-9984: Deprecate GenericHadoopAuthPlugin in favor of HadoopAuthPlugin


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

Branch: refs/heads/jira/solr-9857
Commit: 1a05d6f4f1a6e7c99662549c8f24a11727d86b2f
Parents: 9f58b6c
Author: Ishan Chattopadhyaya <ic...@gmail.com>
Authored: Thu Jan 19 09:35:59 2017 +0530
Committer: Ishan Chattopadhyaya <ic...@gmail.com>
Committed: Thu Jan 19 09:35:59 2017 +0530

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   8 +
 .../solr/security/GenericHadoopAuthPlugin.java  | 245 +------------------
 .../apache/solr/security/HadoopAuthPlugin.java  |   2 +-
 3 files changed, 14 insertions(+), 241 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a05d6f4/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index aab5116..62b8818 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -94,6 +94,12 @@ Jetty 9.3.14.v20161028
 Detailed Change List
 ----------------------
 
+Upgrade Notes
+----------------------
+
+* SOLR-9984: GenericHadoopAuthPlugin is deprecated in favor of HadoopAuthPlugin. Simply changing the
+  name of the class in the security configurations should suffice while upgrading.
+
 New Features
 ----------------------
 
@@ -122,6 +128,8 @@ Other Changes
 ----------------------
 * SOLR-9980: Expose configVersion in core admin status (Jessica Cheng Mallet via Tom�s Fern�ndez L�bbe)
 
+* SOLR-9984: Deprecate GenericHadoopAuthPlugin in favor of HadoopAuthPlugin (Ishan Chattopadhyaya)
+
 ==================  6.4.0 ==================
 
 Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a05d6f4/solr/core/src/java/org/apache/solr/security/GenericHadoopAuthPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/GenericHadoopAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/GenericHadoopAuthPlugin.java
index e5fe349..3d63fd6 100644
--- a/solr/core/src/java/org/apache/solr/security/GenericHadoopAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/GenericHadoopAuthPlugin.java
@@ -16,251 +16,16 @@
  */
 package org.apache.solr.security;
 
-import static org.apache.solr.security.RequestContinuesRecorderAuthenticationHandler.REQUEST_CONTINUES_ATTR;
-import static org.apache.solr.security.HadoopAuthFilter.DELEGATION_TOKEN_ZK_CLIENT;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.lang.invoke.MethodHandles;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-
-import org.apache.commons.collections.iterators.IteratorEnumeration;
-import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
-import org.apache.solr.client.solrj.impl.HttpClientBuilderFactory;
-import org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder;
-import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder;
-import org.apache.solr.cloud.ZkController;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.SolrException.ErrorCode;
-import org.apache.solr.common.util.SuppressForbidden;
 import org.apache.solr.core.CoreContainer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
- * This class implements a generic plugin which can use authentication schemes exposed by the
- * Hadoop framework. This plugin supports following features
- * - integration with authentication mehcanisms (e.g. kerberos)
- * - Delegation token support
- * - Proxy users (or secure impersonation) support
- *
- * This plugin enables defining configuration parameters required by the undelying Hadoop authentication
- * mechanism. These configuration parameters can either be specified as a Java system property or the default
- * value can be specified as part of the plugin configuration.
- *
- * The proxy users are configured by specifying relevant Hadoop configuration parameters. Please note that
- * the delegation token support must be enabled for using the proxy users support.
- *
- * For Solr internal communication, this plugin enables configuring {@linkplain HttpClientBuilderFactory}
- * implementation (e.g. based on kerberos).
+ *  * @deprecated Use {@link HadoopAuthPlugin}. For backcompat against Solr 6.4.
  */
-public class GenericHadoopAuthPlugin extends AuthenticationPlugin implements HttpClientBuilderPlugin {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  /**
-   * A property specifying the type of authentication scheme to be configured.
-   */
-  private static final String HADOOP_AUTH_TYPE = "type";
-
-  /**
-   * A property specifies the value of the prefix to be used to define Java system property
-   * for configuring the authentication mechanism. The name of the Java system property is
-   * defined by appending the configuration parmeter namne to this prefix value e.g. if prefix
-   * is 'solr' then the Java system property 'solr.kerberos.principal' defines the value of
-   * configuration parameter 'kerberos.principal'.
-   */
-  private static final String SYSPROP_PREFIX_PROPERTY = "sysPropPrefix";
-
-  /**
-   * A property specifying the configuration parameters required by the authentication scheme
-   * defined by {@linkplain #HADOOP_AUTH_TYPE} property.
-   */
-  private static final String AUTH_CONFIG_NAMES_PROPERTY = "authConfigs";
-
-  /**
-   * A property specifying the {@linkplain HttpClientBuilderFactory} used for the Solr internal
-   * communication.
-   */
-  private static final String HTTPCLIENT_BUILDER_FACTORY = "clientBuilderFactory";
-
-  /**
-   * A property specifying the default values for the configuration parameters specified by the
-   * {@linkplain #AUTH_CONFIG_NAMES_PROPERTY} property. The default values are specified as a
-   * collection of key-value pairs (i.e. property-name : default_value).
-   */
-  private static final String DEFAULT_AUTH_CONFIGS_PROPERTY = "defaultConfigs";
-
-  /**
-   * A property which enable (or disable) the delegation tokens functionality.
-   */
-  private static final String DELEGATION_TOKEN_ENABLED_PROPERTY = "enableDelegationToken";
-
-  /**
-   * A property which enables initialization of kerberos before connecting to Zookeeper.
-   */
-  private static final String INIT_KERBEROS_ZK = "initKerberosZk";
-
-  /**
-   * A property which configures proxy users for the underlying Hadoop authentication mechanism.
-   * This configuration is expressed as a collection of key-value pairs  (i.e. property-name : value).
-   */
-  public static final String PROXY_USER_CONFIGS = "proxyUserConfigs";
-
-  private AuthenticationFilter authFilter;
-  private HttpClientBuilderFactory factory = null;
-  private final CoreContainer coreContainer;
+@Deprecated
+public class GenericHadoopAuthPlugin extends HadoopAuthPlugin {
 
   public GenericHadoopAuthPlugin(CoreContainer coreContainer) {
-    this.coreContainer = coreContainer;
-  }
-
-  @SuppressWarnings("rawtypes")
-  @Override
-  public void init(Map<String,Object> pluginConfig) {
-    try {
-      String delegationTokenEnabled = (String)pluginConfig.getOrDefault(DELEGATION_TOKEN_ENABLED_PROPERTY, "false");
-      authFilter = (Boolean.parseBoolean(delegationTokenEnabled)) ? new HadoopAuthFilter() : new AuthenticationFilter();
-
-      // Initialize kerberos before initializing curator instance.
-      boolean initKerberosZk = Boolean.parseBoolean((String)pluginConfig.getOrDefault(INIT_KERBEROS_ZK, "false"));
-      if (initKerberosZk) {
-        (new Krb5HttpClientBuilder()).getBuilder();
-      }
-
-      FilterConfig conf = getInitFilterConfig(pluginConfig);
-      authFilter.init(conf);
-
-      String httpClientBuilderFactory = (String)pluginConfig.get(HTTPCLIENT_BUILDER_FACTORY);
-      if (httpClientBuilderFactory != null) {
-        Class c = Class.forName(httpClientBuilderFactory);
-        factory = (HttpClientBuilderFactory)c.newInstance();
-      }
-
-    } catch (ServletException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, "Error initializing kerberos authentication plugin: "+e);
-    }
+    super(coreContainer);
   }
 
-  @SuppressWarnings("unchecked")
-  protected FilterConfig getInitFilterConfig(Map<String, Object> pluginConfig) {
-    Map<String, String> params = new HashMap<>();
-
-    String type = (String) Objects.requireNonNull(pluginConfig.get(HADOOP_AUTH_TYPE));
-    params.put(HADOOP_AUTH_TYPE, type);
-
-    String sysPropPrefix = (String) pluginConfig.getOrDefault(SYSPROP_PREFIX_PROPERTY, "solr.");
-    Collection<String> authConfigNames = (Collection<String>) pluginConfig.
-        getOrDefault(AUTH_CONFIG_NAMES_PROPERTY, Collections.emptyList());
-    Map<String,String> authConfigDefaults = (Map<String,String>) pluginConfig
-        .getOrDefault(DEFAULT_AUTH_CONFIGS_PROPERTY, Collections.emptyMap());
-    Map<String,String> proxyUserConfigs = (Map<String,String>) pluginConfig
-        .getOrDefault(PROXY_USER_CONFIGS, Collections.emptyMap());
-
-    for ( String configName : authConfigNames) {
-      String systemProperty = sysPropPrefix + configName;
-      String defaultConfigVal = authConfigDefaults.get(configName);
-      String configVal = System.getProperty(systemProperty, defaultConfigVal);
-      if (configVal != null) {
-        params.put(configName, configVal);
-      }
-    }
-
-    // Configure proxy user settings.
-    params.putAll(proxyUserConfigs);
-
-    final ServletContext servletContext = new AttributeOnlyServletContext();
-    log.info("Params: "+params);
-
-    ZkController controller = coreContainer.getZkController();
-    if (controller != null) {
-      servletContext.setAttribute(DELEGATION_TOKEN_ZK_CLIENT, controller.getZkClient());
-    }
-
-    FilterConfig conf = new FilterConfig() {
-      @Override
-      public ServletContext getServletContext() {
-        return servletContext;
-      }
-
-      @Override
-      public Enumeration<String> getInitParameterNames() {
-        return new IteratorEnumeration(params.keySet().iterator());
-      }
-
-      @Override
-      public String getInitParameter(String param) {
-        return params.get(param);
-      }
-
-      @Override
-      public String getFilterName() {
-        return "HadoopAuthFilter";
-      }
-    };
-
-    return conf;
-  }
-
-  @Override
-  public boolean doAuthenticate(ServletRequest request, ServletResponse response, FilterChain filterChain)
-      throws Exception {
-    final HttpServletResponse frsp = (HttpServletResponse)response;
-
-    // Workaround until HADOOP-13346 is fixed.
-    HttpServletResponse rspCloseShield = new HttpServletResponseWrapper(frsp) {
-      @SuppressForbidden(reason = "Hadoop DelegationTokenAuthenticationFilter uses response writer, this" +
-          "is providing a CloseShield on top of that")
-      @Override
-      public PrintWriter getWriter() throws IOException {
-        final PrintWriter pw = new PrintWriterWrapper(frsp.getWriter()) {
-          @Override
-          public void close() {};
-        };
-        return pw;
-      }
-    };
-    authFilter.doFilter(request, rspCloseShield, filterChain);
-
-    if (authFilter instanceof HadoopAuthFilter) { // delegation token mgmt.
-      String requestContinuesAttr = (String)request.getAttribute(REQUEST_CONTINUES_ATTR);
-      if (requestContinuesAttr == null) {
-        log.warn("Could not find " + REQUEST_CONTINUES_ATTR);
-        return false;
-      } else {
-        return Boolean.parseBoolean(requestContinuesAttr);
-      }
-    }
-
-    return true;
-  }
-
-  @Override
-  public SolrHttpClientBuilder getHttpClientBuilder(SolrHttpClientBuilder builder) {
-    return (factory != null) ? factory.getHttpClientBuilder(Optional.ofNullable(builder)) : builder;
-  }
-
-  @Override
-  public void close() throws IOException {
-    if (authFilter != null) {
-      authFilter.destroy();
-    }
-    if (factory != null) {
-      factory.close();
-    }
-  }
-}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a05d6f4/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
index db0f639..1f0d5ad 100644
--- a/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
@@ -135,7 +135,7 @@ public class HadoopAuthPlugin extends AuthenticationPlugin {
       authFilter.init(conf);
 
     } catch (ServletException e) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, "Error initializing GenericHadoopAuthPlugin: "+e);
+      throw new SolrException(ErrorCode.SERVER_ERROR, "Error initializing " + getClass().getName() + ": "+e);
     }
   }
 


[29/38] lucene-solr:jira/solr-9857: SOLR-8396: Add support for PointFields in Solr

Posted by ab...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test-files/solr/collection1/conf/schema-point.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema-point.xml b/solr/core/src/test-files/solr/collection1/conf/schema-point.xml
new file mode 100644
index 0000000..ca37ff5
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/schema-point.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ 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.
+-->
+
+<schema name="example" version="1.6">
+  <types>
+    <fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
+
+    <fieldType name="pint" class="solr.IntPointField"/>
+    <fieldType name="plong" class="solr.LongPointField"/>
+    <fieldType name="pdouble" class="solr.DoublePointField"/>
+    <fieldType name="pfloat" class="solr.FloatPointField"/>
+    
+    <fieldType name="int" class="solr.TrieIntField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
+    <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
+    <fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
+    <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
+    <fieldType name="date" class="solr.TrieDateField" sortMissingLast="true" omitNorms="true"/>
+ </types>
+
+
+ <fields>
+   <field name="id" type="string"/>
+   <field name="text" type="string"/>
+   <field name="_version_" type="long" indexed="true" stored="true" multiValued="false" />
+   <field name="signatureField" type="string" indexed="true" stored="false"/>
+   <dynamicField name="*_s"  type="string"  indexed="true"  stored="true"/>
+   <dynamicField name="*_sS" type="string"  indexed="false" stored="true"/>
+   <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
+   <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
+   <dynamicField name="*_f"  type="float"  indexed="true"  stored="true"/>
+   <dynamicField name="*_d"  type="double" indexed="true"  stored="true"/>
+   <dynamicField name="*_dt"  type="date" indexed="true"  stored="true"/>
+   
+   <dynamicField name="*_p_i"  type="pint"    indexed="true"  stored="true"/>
+   <dynamicField name="*_p_i_dv"  type="pint"    indexed="true"  stored="true" docValues="true"/>
+   <dynamicField name="*_p_i_mv"  type="pint"    indexed="true"  stored="true" multiValued="true"/>
+   <dynamicField name="*_p_i_mv_dv"  type="pint"    indexed="true"  stored="true" docValues="true" multiValued="true"/>
+   <dynamicField name="*_p_i_ni_dv"  type="pint"    indexed="false"  stored="true" docValues="true"/>
+   <dynamicField name="*_p_i_ni_mv_dv"  type="pint"    indexed="false"  stored="true" docValues="true" multiValued="true"/>
+   
+   <dynamicField name="*_p_l"  type="plong"    indexed="true"  stored="true"/>
+   <dynamicField name="*_p_l_dv"  type="plong"    indexed="true"  stored="true" docValues="true"/>
+   <dynamicField name="*_p_l_mv"  type="plong"    indexed="true"  stored="true" multiValued="true"/>
+   <dynamicField name="*_p_l_mv_dv"  type="plong"    indexed="true"  stored="true" docValues="true" multiValued="true"/>
+   <dynamicField name="*_p_l_ni_dv"  type="plong"    indexed="false"  stored="true" docValues="true"/>
+   <dynamicField name="*_p_l_ni_mv_dv"  type="plong"    indexed="false"  stored="true" docValues="true" multiValued="true"/>
+   
+   <dynamicField name="*_p_d"  type="pdouble"    indexed="true"  stored="true"/>
+   <dynamicField name="*_p_d_dv"  type="pdouble"    indexed="true"  stored="true" docValues="true"/>
+   <dynamicField name="*_p_d_mv"  type="pdouble"    indexed="true"  stored="true" multiValued="true"/>
+   <dynamicField name="*_p_d_mv_dv"  type="pdouble"    indexed="true"  stored="true" docValues="true" multiValued="true"/>
+   <dynamicField name="*_p_d_ni_dv"  type="pdouble"    indexed="false"  stored="true" docValues="true"/>
+   <dynamicField name="*_p_d_ni_mv_dv"  type="pdouble"    indexed="false"  stored="true" docValues="true" multiValued="true"/>
+   
+   <dynamicField name="*_p_f"  type="pfloat"    indexed="true"  stored="true"/>
+   <dynamicField name="*_p_f_dv"  type="pfloat"    indexed="true"  stored="true" docValues="true"/>
+   <dynamicField name="*_p_f_mv"  type="pfloat"    indexed="true"  stored="true" multiValued="true"/>
+   <dynamicField name="*_p_f_mv_dv"  type="pfloat"    indexed="true"  stored="true" docValues="true" multiValued="true"/>
+   <dynamicField name="*_p_f_ni_dv"  type="pfloat"    indexed="false"  stored="true" docValues="true"/>
+   <dynamicField name="*_p_f_ni_mv_dv"  type="pfloat"    indexed="false"  stored="true" docValues="true" multiValued="true"/>
+   
+   <!-- return DV fields as  -->
+   <dynamicField name="*_p_i_dv_ns"  type="pint"    indexed="true"  stored="false" docValues="true" useDocValuesAsStored="true"/>
+   <dynamicField name="*_p_l_dv_ns"  type="plong"    indexed="true"  stored="false" docValues="true" useDocValuesAsStored="true"/>
+   <dynamicField name="*_p_d_dv_ns"  type="pdouble"    indexed="true"  stored="false" docValues="true" useDocValuesAsStored="true"/>
+   <dynamicField name="*_p_f_dv_ns"  type="pfloat"    indexed="true"  stored="false" docValues="true" useDocValuesAsStored="true"/>
+
+ </fields>
+
+ <uniqueKey>id</uniqueKey>
+
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test-files/solr/collection1/conf/schema-sorts.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema-sorts.xml b/solr/core/src/test-files/solr/collection1/conf/schema-sorts.xml
index c918017..f68841c 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema-sorts.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema-sorts.xml
@@ -45,30 +45,30 @@ NOTE: Tests expect every field in this schema to be sortable.
   <field name="int" type="int"/>
   <field name="int_last" type="int_last"/>
   <field name="int_first" type="int_first"/>
-  <field name="int_dv" type="int_dv"/>
-  <field name="int_dv_last" type="int_dv_last"/>
-  <field name="int_dv_first" type="int_dv_first"/>
+  <field name="int_dv" type="${solr.tests.intClass:pint}_dv"/>
+  <field name="int_dv_last" type="${solr.tests.intClass:pint}_dv_last"/>
+  <field name="int_dv_first" type="${solr.tests.intClass:pint}_dv_first"/>
 
   <field name="long" type="long"/>
   <field name="long_last" type="long_last"/>
   <field name="long_first" type="long_first"/>
-  <field name="long_dv" type="long_dv"/>
-  <field name="long_dv_last" type="long_dv_last"/>
-  <field name="long_dv_first" type="long_dv_first"/>
+  <field name="long_dv" type="${solr.tests.longClass:plong}_dv"/>
+  <field name="long_dv_last" type="${solr.tests.longClass:plong}_dv_last"/>
+  <field name="long_dv_first" type="${solr.tests.longClass:plong}_dv_first"/>
 
   <field name="float" type="float"/>
   <field name="float_last" type="float_last"/>
   <field name="float_first" type="float_first"/>
-  <field name="float_dv" type="float_dv"/>
-  <field name="float_dv_last" type="float_dv_last"/>
-  <field name="float_dv_first" type="float_dv_first"/>
+  <field name="float_dv" type="${solr.tests.floatClass:pfloat}_dv"/>
+  <field name="float_dv_last" type="${solr.tests.floatClass:pfloat}_dv_last"/>
+  <field name="float_dv_first" type="${solr.tests.floatClass:pfloat}_dv_first"/>
 
   <field name="double" type="double"/>
   <field name="double_last" type="double_last"/>
   <field name="double_first" type="double_first"/>
-  <field name="double_dv" type="double_dv"/>
-  <field name="double_dv_last" type="double_dv_last"/>
-  <field name="double_dv_first" type="double_dv_first"/>
+  <field name="double_dv" type="${solr.tests.doubleClass:pdouble}_dv"/>
+  <field name="double_dv_last" type="${solr.tests.doubleClass:pdouble}_dv_last"/>
+  <field name="double_dv_first" type="${solr.tests.doubleClass:pdouble}_dv_first"/>
 
   <field name="date" type="date"/>
   <field name="date_last" type="date_last"/>
@@ -220,6 +220,11 @@ NOTE: Tests expect every field in this schema to be sortable.
              sortMissingLast="true"/>
   <fieldType name="int_dv_first" class="solr.TrieIntField" stored="true" indexed="false" docValues="true"
              sortMissingFirst="true"/>
+  <fieldType name="pint_dv" class="solr.IntPointField" stored="true" indexed="false" docValues="true"/>
+  <fieldType name="pint_dv_last" class="solr.IntPointField" stored="true" indexed="false" docValues="true"
+             sortMissingLast="true"/>
+  <fieldType name="pint_dv_first" class="solr.IntPointField" stored="true" indexed="false" docValues="true"
+             sortMissingFirst="true"/>
 
   <fieldType name="long" class="solr.TrieLongField" stored="true" indexed="true"/>
   <fieldType name="long_last" class="solr.TrieLongField" stored="true" indexed="true" sortMissingLast="true"/>
@@ -229,6 +234,11 @@ NOTE: Tests expect every field in this schema to be sortable.
              sortMissingLast="true"/>
   <fieldType name="long_dv_first" class="solr.TrieLongField" stored="true" indexed="false" docValues="true"
              sortMissingFirst="true"/>
+  <fieldType name="plong_dv" class="solr.LongPointField" stored="true" indexed="false" docValues="true"/>
+  <fieldType name="plong_dv_last" class="solr.LongPointField" stored="true" indexed="false" docValues="true"
+             sortMissingLast="true"/>
+  <fieldType name="plong_dv_first" class="solr.LongPointField" stored="true" indexed="false" docValues="true"
+             sortMissingFirst="true"/>
 
   <fieldType name="float" class="solr.TrieFloatField" stored="true" indexed="true"/>
   <fieldType name="float_last" class="solr.TrieFloatField" stored="true" indexed="true" sortMissingLast="true"/>
@@ -238,6 +248,11 @@ NOTE: Tests expect every field in this schema to be sortable.
              sortMissingLast="true"/>
   <fieldType name="float_dv_first" class="solr.TrieFloatField" stored="true" indexed="false" docValues="true"
              sortMissingFirst="true"/>
+  <fieldType name="pfloat_dv" class="solr.FloatPointField" stored="true" indexed="false" docValues="true"/>
+  <fieldType name="pfloat_dv_last" class="solr.FloatPointField" stored="true" indexed="false" docValues="true"
+             sortMissingLast="true"/>
+  <fieldType name="pfloat_dv_first" class="solr.FloatPointField" stored="true" indexed="false" docValues="true"
+             sortMissingFirst="true"/>
 
   <fieldType name="double" class="solr.TrieDoubleField" stored="true" indexed="true"/>
   <fieldType name="double_last" class="solr.TrieDoubleField" stored="true" indexed="true" sortMissingLast="true"/>
@@ -247,6 +262,11 @@ NOTE: Tests expect every field in this schema to be sortable.
              sortMissingLast="true"/>
   <fieldType name="double_dv_first" class="solr.TrieDoubleField" stored="true" indexed="false" docValues="true"
              sortMissingFirst="true"/>
+  <fieldType name="pdouble_dv" class="solr.DoublePointField" stored="true" indexed="false" docValues="true"/>
+  <fieldType name="pdouble_dv_last" class="solr.DoublePointField" stored="true" indexed="false" docValues="true"
+             sortMissingLast="true"/>
+  <fieldType name="pdouble_dv_first" class="solr.DoublePointField" stored="true" indexed="false" docValues="true"
+             sortMissingFirst="true"/>
 
   <fieldType name="date" class="solr.TrieDateField" stored="true" indexed="true"/>
   <fieldType name="date_last" class="solr.TrieDateField" stored="true" indexed="true" sortMissingLast="true"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test-files/solr/collection1/conf/schema.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema.xml b/solr/core/src/test-files/solr/collection1/conf/schema.xml
index 35de166..be1b6f5 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema.xml
@@ -43,6 +43,12 @@
   <fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
   <fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
   <fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+  
+  <!-- Point Fields -->
+  <fieldType name="pint" class="solr.IntPointField" docValues="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+  <fieldType name="pdouble" class="solr.DoublePointField" docValues="true"/>
+  <fieldType name="pfloat" class="solr.FloatPointField" docValues="true"/>
 
   <!-- Field type demonstrating an Analyzer failure -->
   <fieldType name="failtype1" class="solr.TextField">
@@ -550,7 +556,7 @@
   <field name="dateRange" type="dateRange" multiValued="true"/>
 
   <field name="cat" type="string" indexed="true" stored="true" multiValued="true"/>
-  <field name="price" type="float" indexed="true" stored="true" multiValued="false"/>
+  <field name="price" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="true" multiValued="false"/>
   <field name="inStock" type="boolean" indexed="true" stored="true"/>
 
   <field name="subword" type="subword" indexed="true" stored="true"/>
@@ -599,7 +605,7 @@
   -->
   <dynamicField name="*_i" type="int" indexed="true" stored="true"/>
   <dynamicField name="*_i1" type="int" indexed="true" stored="true" multiValued="false" sortMissingLast="true"/>
-  <dynamicField name="*_idv" type="int" indexed="true" stored="true" docValues="true" multiValued="false"/>
+  <dynamicField name="*_idv" type="${solr.tests.intClass:pint}" indexed="true" stored="true" docValues="true" multiValued="false"/>
 
 
   <dynamicField name="*_s" type="string" indexed="true" stored="true"/>
@@ -610,9 +616,9 @@
   <dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
   <dynamicField name="*_b1" type="boolean" indexed="true" stored="true" multiValued="false"/>
   <dynamicField name="*_f" type="float" indexed="true" stored="true"/>
-  <dynamicField name="*_f1" type="float" indexed="true" stored="true" multiValued="false"/>
+  <dynamicField name="*_f1" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="true" multiValued="false"/>
   <dynamicField name="*_d" type="double" indexed="true" stored="true"/>
-  <dynamicField name="*_d1" type="double" indexed="true" stored="true" multiValued="false"/>
+  <dynamicField name="*_d1" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="true" multiValued="false"/>
   <dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
   <dynamicField name="*_dt1" type="date" indexed="true" stored="true" multiValued="false"/>
 
@@ -662,7 +668,7 @@
   <dynamicField name="*_mfacet" type="string" indexed="true" stored="false" multiValued="true"/>
 
   <!-- Type used to index the lat and lon components for the "location" FieldType -->
-  <dynamicField name="*_coordinate" type="tdouble" indexed="true" stored="false" omitNorms="true"/>
+  <dynamicField name="*_coordinate" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="false" omitNorms="true"/>
 
   <dynamicField name="*_path" type="path" indexed="true" stored="true" omitNorms="true" multiValued="true"/>
   <dynamicField name="*_ancestor" type="ancestor_path" indexed="true" stored="true" omitNorms="true"
@@ -676,12 +682,12 @@
   <dynamicField name="*_f_dv" type="float" indexed="true" stored="true" docValues="true"/>
   <dynamicField name="*_d_dv" type="double" indexed="true" stored="true" docValues="true"/>
   <dynamicField name="*_dt_dv" type="date" indexed="true" stored="true" docValues="true"/>
-  <dynamicField name="*_f1_dv" type="float" indexed="true" stored="true" docValues="true" multiValued="false"/>
+  <dynamicField name="*_f1_dv" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="true" docValues="true" multiValued="false"/>
 
   <!--  Non-stored, DocValues=true -->
-  <dynamicField name="*_i_dvo" multiValued="false" type="int" docValues="true" indexed="true" stored="false"
+  <dynamicField name="*_i_dvo" multiValued="false" type="${solr.tests.intClass:pint}" docValues="true" indexed="true" stored="false"
                 useDocValuesAsStored="true"/>
-  <dynamicField name="*_d_dvo" multiValued="false" type="double" docValues="true" indexed="true" stored="false"
+  <dynamicField name="*_d_dvo" multiValued="false" type="${solr.tests.doubleClass:pdouble}" docValues="true" indexed="true" stored="false"
                 useDocValuesAsStored="true"/>
   <dynamicField name="*_s_dvo" multiValued="false" type="string" docValues="true" indexed="true" stored="false"
                 useDocValuesAsStored="true"/>
@@ -691,8 +697,8 @@
                 useDocValuesAsStored="true"/>
 
   <!--  Non-stored, DocValues=true, useDocValuesAsStored=false -->
-  <field name="single_i_dvn" multiValued="false" type="int" indexed="true" stored="true"/>
-  <field name="single_d_dvn" multiValued="false" type="double" indexed="true" stored="true"/>
+  <field name="single_i_dvn" multiValued="false" type="${solr.tests.intClass:pint}" indexed="true" stored="true"/>
+  <field name="single_d_dvn" multiValued="false" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="true"/>
   <field name="single_s_dvn" multiValued="false" type="string" indexed="true" stored="true"/>
   <field name="copy_single_i_dvn" multiValued="false" type="int" docValues="true" indexed="true" stored="false"
          useDocValuesAsStored="false"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test-files/solr/collection1/conf/schema11.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema11.xml b/solr/core/src/test-files/solr/collection1/conf/schema11.xml
index e5b9233..370f321 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema11.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema11.xml
@@ -77,7 +77,12 @@
   <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" positionIncrementGap="0"/>
   <fieldType name="long" class="solr.TrieLongField" precisionStep="0" positionIncrementGap="0"/>
   <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" positionIncrementGap="0"/>
-
+  
+  <!-- Point Fields -->
+  <fieldType name="pint" class="solr.IntPointField" docValues="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+  <fieldType name="pdouble" class="solr.DoublePointField" docValues="true"/>
+  <fieldType name="pfloat" class="solr.FloatPointField" docValues="true"/>
 
     <!-- The format for this date field is of the form 1995-12-31T23:59:59Z, and
          is a more restricted form of the canonical representation of dateTime
@@ -340,15 +345,15 @@ valued. -->
    <dynamicField name="*_s_dv"  type="string"  indexed="true"  stored="true" docValues="true"/>
    <dynamicField name="*_ss"    type="string"  indexed="true"  stored="true" multiValued="true"/>
    <dynamicField name="*_sS"   type="string"  indexed="false" stored="true"/>
-   <dynamicField name="*_i"    type="int"    indexed="true"  stored="true"/>
+   <dynamicField name="*_i"    type="${solr.tests.intClass:pint}"    indexed="true"  stored="true"/>
    <dynamicField name="*_ii"   type="int"    indexed="true"  stored="true" multiValued="true"/>
-   <dynamicField name="*_l"    type="long"   indexed="true"  stored="true"/>
-   <dynamicField name="*_f"    type="float"  indexed="true"  stored="true"/>
-   <dynamicField name="*_d"    type="double" indexed="true"  stored="true"/>
+   <dynamicField name="*_l"    type="${solr.tests.longClass:plong}"   indexed="true"  stored="true"/>
+   <dynamicField name="*_f"    type="${solr.tests.floatClass:pfloat}"  indexed="true"  stored="true"/>
+   <dynamicField name="*_d"    type="${solr.tests.doubleClass:pdouble}" indexed="true"  stored="true"/>
 
    <dynamicField name="*_ti"      type="tint"    indexed="true"  stored="true"/>
-   <dynamicField name="*_ti_dv"   type="int"    indexed="true"  stored="true" docValues="true"/>
-   <dynamicField name="*_ti_ni_dv"   type="int"    indexed="true"  stored="true" docValues="true"/>
+   <dynamicField name="*_ti_dv"   type="tint"    indexed="true"  stored="true" docValues="true"/>
+   <dynamicField name="*_ti_ni_dv"   type="tint"    indexed="true"  stored="true" docValues="true"/>
    <dynamicField name="*_tl"      type="tlong"   indexed="true"  stored="true"/>
    <dynamicField name="*_tl_dv"    type="tlong"   indexed="true"  stored="true" docValues="true"/>
    <dynamicField name="*_tl_ni_dv"   type="tlong"   indexed="false"  stored="true" docValues="true"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test-files/solr/collection1/conf/schema12.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema12.xml b/solr/core/src/test-files/solr/collection1/conf/schema12.xml
index 0de219a..206cd9e 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema12.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema12.xml
@@ -42,6 +42,12 @@
   <fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
   <fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
   <fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+  
+  <!-- Point Fields -->
+  <fieldType name="pint" class="solr.IntPointField" docValues="true"/>
+  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
+  <fieldType name="pdouble" class="solr.DoublePointField" docValues="true"/>
+  <fieldType name="pfloat" class="solr.FloatPointField" docValues="true"/>
 
   <!-- Field type demonstrating an Analyzer failure -->
   <fieldType name="failtype1" class="solr.TextField">
@@ -432,7 +438,7 @@
   <field name="text" type="text" indexed="true" stored="false"/>
   <field name="subject" type="text" indexed="true" stored="true"/>
   <field name="title" type="nametext" indexed="true" stored="true"/>
-  <field name="weight" type="float" indexed="true" stored="true"/>
+  <field name="weight" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="true"/>
   <field name="bday" type="date" indexed="true" stored="true"/>
 
   <field name="text_np" type="text_np" indexed="true" stored="false"/>
@@ -550,7 +556,8 @@
 
   <dynamicField name="*_i" type="int" indexed="true" stored="true"/>
   <dynamicField name="*_is" type="int" indexed="true" stored="true" multiValued="true"/>
-  <dynamicField name="*_idv" type="int" indexed="true" stored="true" docValues="true" multiValued="false"/>
+  <dynamicField name="*_i_dv" type="${solr.tests.intClass:pint}" indexed="true" stored="true" docValues="true" multiValued="false"/>
+  <dynamicField name="*_is_dv" type="${solr.tests.intClass:pint}" indexed="true" stored="true" docValues="true" multiValued="false"/>
   <dynamicField name="*_s1" type="string" indexed="true" stored="true" multiValued="false"/>
   <!-- :TODO: why are these identical?!?!?! -->
   <dynamicField name="*_s" type="string" indexed="true" stored="true" multiValued="true"/>
@@ -559,9 +566,11 @@
   <dynamicField name="*_t" type="text" indexed="true" stored="true"/>
   <dynamicField name="*_tt" type="text" indexed="true" stored="true"/>
   <dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
-  <dynamicField name="*_f" type="float" indexed="true" stored="true"/>
+  <dynamicField name="*_f" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="true"/>
   <dynamicField name="*_d" type="double" indexed="true" stored="true"/>
   <dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
+  
+  <dynamicField name="*_pi" type="pint" indexed="true" stored="true" docValues="false" multiValued="false"/>
 
   <!-- some trie-coded dynamic fields for faster range queries -->
   <dynamicField name="*_ti" type="tint" indexed="true" stored="true"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test-files/solr/collection1/conf/schema_latest.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema_latest.xml b/solr/core/src/test-files/solr/collection1/conf/schema_latest.xml
index 280fd34..c6491eb 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema_latest.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema_latest.xml
@@ -202,7 +202,7 @@
   <!-- docvalues and stored are exclusive -->
   <dynamicField name="*_i" type="int" indexed="true" stored="true"/>
   <dynamicField name="*_is" type="int" indexed="true" stored="true" multiValued="true"/>
-  <dynamicField name="*_id" type="int" indexed="true" stored="false" docValues="true"/>
+  <dynamicField name="*_id" type="${solr.tests.intClass:pint}" indexed="true" stored="false" docValues="true"/>
   <dynamicField name="*_ids" type="int" indexed="true" stored="false" multiValued="true" docValues="true"/>
   <dynamicField name="*_s" type="string" indexed="true" stored="true"/>
   <dynamicField name="*_s1" type="string" indexed="true" stored="true"/>
@@ -215,11 +215,11 @@
   <dynamicField name="*_lds" type="long" indexed="true" stored="false" multiValued="true" docValues="true"/>
   <dynamicField name="*_f" type="float" indexed="true" stored="true"/>
   <dynamicField name="*_fs" type="float" indexed="true" stored="true" multiValued="true"/>
-  <dynamicField name="*_fd" type="float" indexed="true" stored="false" docValues="true"/>
+  <dynamicField name="*_fd" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="false" docValues="true"/>
   <dynamicField name="*_fds" type="float" indexed="true" stored="false" multiValued="true" docValues="true"/>
   <dynamicField name="*_d" type="double" indexed="true" stored="true"/>
   <dynamicField name="*_ds" type="double" indexed="true" stored="true" multiValued="true"/>
-  <dynamicField name="*_dd" type="double" indexed="true" stored="false" docValues="true"/>
+  <dynamicField name="*_dd" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="false" docValues="true"/>
   <dynamicField name="*_dds" type="double" indexed="true" stored="false" multiValued="true" docValues="true"/>
   <dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
   <dynamicField name="*_dts" type="date" indexed="true" stored="true" multiValued="true"/>
@@ -227,15 +227,15 @@
   <dynamicField name="*_dtds" type="date" indexed="true" stored="false" multiValued="true" docValues="true"/>
 
   <!-- docvalues and stored (S suffix) -->
-  <dynamicField name="*_idS" type="int" indexed="true" stored="true" docValues="true"/>
+  <dynamicField name="*_idS" type="${solr.tests.intClass:pint}" indexed="true" stored="true" docValues="true"/>
   <dynamicField name="*_idsS" type="int" indexed="true" stored="true" multiValued="true" docValues="true"/>
   <dynamicField name="*_sdS" type="string" indexed="true" stored="true" docValues="true"/>
   <dynamicField name="*_sdsS" type="string" indexed="true" stored="true" multiValued="true" docValues="true"/>
-  <dynamicField name="*_ldS" type="long" indexed="true" stored="true" docValues="true"/>
+  <dynamicField name="*_ldS" type="${solr.tests.longClass:plong}" indexed="true" stored="true" docValues="true"/>
   <dynamicField name="*_ldsS" type="long" indexed="true" stored="true" multiValued="true" docValues="true"/>
-  <dynamicField name="*_fdS" type="float" indexed="true" stored="true" docValues="true"/>
+  <dynamicField name="*_fdS" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="true" docValues="true"/>
   <dynamicField name="*_fdsS" type="float" indexed="true" stored="true" multiValued="true" docValues="true"/>
-  <dynamicField name="*_ddS" type="double" indexed="true" stored="true" docValues="true"/>
+  <dynamicField name="*_ddS" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="true" docValues="true"/>
   <dynamicField name="*_ddsS" type="double" indexed="true" stored="true" multiValued="true" docValues="true"/>
   <dynamicField name="*_dtdS" type="date" indexed="true" stored="true" docValues="true"/>
   <dynamicField name="*_dtdsS" type="date" indexed="true" stored="true" multiValued="true" docValues="true"/>
@@ -394,6 +394,13 @@
   <fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" positionIncrementGap="0"/>
   <fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" positionIncrementGap="0"/>
   <fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" positionIncrementGap="0"/>
+  
+  <!-- Point Fields -->
+  <fieldType name="pint" class="solr.IntPointField"/>
+  <fieldType name="plong" class="solr.LongPointField"/>
+  <fieldType name="pdouble" class="solr.DoublePointField"/>
+  <fieldType name="pfloat" class="solr.FloatPointField"/>
+  
 
   <!-- The format for this date field is of the form 1995-12-31T23:59:59Z, and
        is a more restricted form of the canonical representation of dateTime

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/TestDistributedGrouping.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/TestDistributedGrouping.java b/solr/core/src/test/org/apache/solr/TestDistributedGrouping.java
index ad62fcc..d0c4f36 100644
--- a/solr/core/src/test/org/apache/solr/TestDistributedGrouping.java
+++ b/solr/core/src/test/org/apache/solr/TestDistributedGrouping.java
@@ -16,19 +16,20 @@
  */
 package org.apache.solr;
 
+import java.io.IOException;
+import java.util.List;
+
 import org.apache.lucene.util.LuceneTestCase.Slow;
+import org.apache.solr.SolrTestCaseJ4.SuppressPointFields;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.SolrDocumentList;
 import org.junit.Test;
 
-import java.io.IOException;
-import java.util.List;
-
 /**
  * TODO? perhaps use:
  *  http://docs.codehaus.org/display/JETTY/ServletTester
@@ -37,6 +38,7 @@ import java.util.List;
  * @since solr 4.0
  */
 @Slow
+@SuppressPointFields
 public class TestDistributedGrouping extends BaseDistributedSearchTestCase {
 
   String t1="a_t";

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/TestJoin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/TestJoin.java b/solr/core/src/test/org/apache/solr/TestJoin.java
index 419275c..f4b2cf5 100644
--- a/solr/core/src/test/org/apache/solr/TestJoin.java
+++ b/solr/core/src/test/org/apache/solr/TestJoin.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr;
 
+import org.apache.solr.SolrTestCaseJ4.SuppressPointFields;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.noggit.JSONUtil;
 import org.noggit.ObjectBuilder;
@@ -36,6 +37,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+@SuppressPointFields
 public class TestJoin extends SolrTestCaseJ4 {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -151,7 +153,7 @@ public class TestJoin extends SolrTestCaseJ4 {
     // increase test effectiveness by avoiding 0 resultsets much of the time.
     String[][] compat = new String[][] {
         {"small_s","small2_s","small2_ss","small3_ss"},
-        {"small_i","small2_i","small2_is","small3_is"}
+        {"small_i","small2_i","small2_is","small3_is", "small_i_dv", "small_is_dv"}
     };
 
 
@@ -169,6 +171,8 @@ public class TestJoin extends SolrTestCaseJ4 {
       types.add(new FldType("small2_i",ZERO_ONE, new IRange(0,5+indexSize/3)));
       types.add(new FldType("small2_is",ZERO_TWO, new IRange(0,5+indexSize/3)));
       types.add(new FldType("small3_is",new IRange(0,25), new IRange(0,100)));
+      types.add(new FldType("small_i_dv",ZERO_ONE, new IRange(0,5+indexSize/3)));
+      types.add(new FldType("small_is_dv",ZERO_ONE, new IRange(0,5+indexSize/3)));
 
       clearIndex();
       Map<Comparable, Doc> model = indexDocs(types, null, indexSize);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/TestRandomDVFaceting.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/TestRandomDVFaceting.java b/solr/core/src/test/org/apache/solr/TestRandomDVFaceting.java
index 90c2394..d088924 100644
--- a/solr/core/src/test/org/apache/solr/TestRandomDVFaceting.java
+++ b/solr/core/src/test/org/apache/solr/TestRandomDVFaceting.java
@@ -62,7 +62,9 @@ public class TestRandomDVFaceting extends SolrTestCaseJ4 {
     types = new ArrayList<>();
     types.add(new FldType("id",ONE_ONE, new SVal('A','Z',4,4)));
     types.add(new FldType("score_f",ONE_ONE, new FVal(1,100)));
+    types.add(new FldType("score_d",ONE_ONE, new FVal(1,100)));
     types.add(new FldType("foo_i",ZERO_ONE, new IRange(0,indexSize)));
+    types.add(new FldType("foo_l",ZERO_ONE, new IRange(0,indexSize)));
     types.add(new FldType("small_s",ZERO_ONE, new SVal('a',(char)('c'+indexSize/3),1,1)));
     types.add(new FldType("small2_s",ZERO_ONE, new SVal('a',(char)('c'+indexSize/3),1,1)));
     types.add(new FldType("small2_ss",ZERO_TWO, new SVal('a',(char)('c'+indexSize/3),1,1)));
@@ -231,6 +233,12 @@ public class TestRandomDVFaceting extends SolrTestCaseJ4 {
 
         responses.add(strResponse);
       }
+      // If there is a PointField option for this test, also test it
+      if (h.getCore().getLatestSchema().getFieldOrNull(facet_field + "_p") != null) {
+        params.set("facet.field", "{!key="+facet_field+"}"+facet_field+"_p");
+        String strResponse = h.query(req(params));
+        responses.add(strResponse);
+      }
 
       /**
       String strResponse = h.query(req(params));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/TestRandomFaceting.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/TestRandomFaceting.java b/solr/core/src/test/org/apache/solr/TestRandomFaceting.java
index 2ffefdc..c3a3e02 100644
--- a/solr/core/src/test/org/apache/solr/TestRandomFaceting.java
+++ b/solr/core/src/test/org/apache/solr/TestRandomFaceting.java
@@ -184,7 +184,6 @@ public class TestRandomFaceting extends SolrTestCaseJ4 {
     SolrQueryRequest req = req();
     try {
       Random rand = random();
-      boolean validate = validateResponses;
       ModifiableSolrParams params = params("facet","true", "wt","json", "indent","true", "omitHeader","true");
       params.add("q","*:*");  // TODO: select subsets
       params.add("rows","0");
@@ -244,8 +243,9 @@ public class TestRandomFaceting extends SolrTestCaseJ4 {
 
       List<String> methods = multiValued ? multiValuedMethods : singleValuedMethods;
       List<String> responses = new ArrayList<>(methods.size());
+      
       for (String method : methods) {
-        for (boolean exists : new boolean [] {false, true}) {
+        for (boolean exists : new boolean[]{false, true}) {
           // params.add("facet.field", "{!key="+method+"}" + ftype.fname);
           // TODO: allow method to be passed on local params?
           if (method!=null) {
@@ -253,7 +253,6 @@ public class TestRandomFaceting extends SolrTestCaseJ4 {
           } else {
             params.remove("facet.method");
           }
-          
           params.set("facet.exists", ""+exists);
           if (!exists && rand.nextBoolean()) {
             params.remove("facet.exists");
@@ -275,6 +274,13 @@ public class TestRandomFaceting extends SolrTestCaseJ4 {
                   "facet.exists", req(params), ErrorCode.BAD_REQUEST);
               continue;
             }
+            if (exists && sf.getType().isPointField()) {
+              // PointFields don't yet support "enum" method or the "facet.exists" parameter
+              assertQEx("Expecting failure, since ", 
+                  "facet.exists=true is requested, but facet.method=enum can't be used with " + sf.getName(), 
+                  req(params), ErrorCode.BAD_REQUEST);
+              continue;
+            }
           }
           String strResponse = h.query(req(params));
           responses.add(strResponse);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/cloud/TestCloudPivotFacet.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestCloudPivotFacet.java b/solr/core/src/test/org/apache/solr/cloud/TestCloudPivotFacet.java
index 460c501..b13cb78 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestCloudPivotFacet.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestCloudPivotFacet.java
@@ -29,6 +29,7 @@ import java.util.Set;
 import org.apache.commons.lang.StringUtils;
 import org.apache.lucene.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
+import org.apache.solr.SolrTestCaseJ4.SuppressPointFields;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.response.FieldStatsInfo;
 import org.apache.solr.client.solrj.response.PivotField;
@@ -78,6 +79,7 @@ import static org.apache.solr.common.params.FacetParams.FACET_DISTRIB_MCO;
  *
  */
 @SuppressSSL // Too Slow
+@SuppressPointFields
 public class TestCloudPivotFacet extends AbstractFullDistribZkTestBase {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/handler/XsltUpdateRequestHandlerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/XsltUpdateRequestHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/XsltUpdateRequestHandlerTest.java
index a7fb774..7062b43 100644
--- a/solr/core/src/test/org/apache/solr/handler/XsltUpdateRequestHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/XsltUpdateRequestHandlerTest.java
@@ -40,7 +40,7 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class XsltUpdateRequestHandlerTest extends SolrTestCaseJ4 {
-
+  
   @BeforeClass
   public static void beforeTests() throws Exception {
     initCore("solrconfig.xml","schema.xml");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/handler/admin/LukeRequestHandlerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/LukeRequestHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/LukeRequestHandlerTest.java
index 66c378d..92b4943 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/LukeRequestHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/LukeRequestHandlerTest.java
@@ -16,19 +16,19 @@
  */
 package org.apache.solr.handler.admin;
 
+import java.util.Arrays;
+import java.util.EnumSet;
+
 import org.apache.solr.common.luke.FieldFlag;
 import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.CustomAnalyzerStrField; // jdoc
+import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.util.AbstractSolrTestCase;
 import org.apache.solr.util.TestHarness;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import java.util.Arrays;
-import java.util.EnumSet;
-
 /**
  * :TODO: currently only tests some of the utilities in the LukeRequestHandler
  */

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/handler/component/TestExpandComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/component/TestExpandComponent.java b/solr/core/src/test/org/apache/solr/handler/component/TestExpandComponent.java
index f7fc13a..7baa5a9 100644
--- a/solr/core/src/test/org/apache/solr/handler/component/TestExpandComponent.java
+++ b/solr/core/src/test/org/apache/solr/handler/component/TestExpandComponent.java
@@ -16,15 +16,19 @@
  */
 package org.apache.solr.handler.component;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.SolrTestCaseJ4.SuppressPointFields;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.search.CollapsingQParserPlugin;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import java.util.*;
-
+@SuppressPointFields
 public class TestExpandComponent extends SolrTestCaseJ4 {
 
   @BeforeClass

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/57934ba4/solr/core/src/test/org/apache/solr/request/TestFacetMethods.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/request/TestFacetMethods.java b/solr/core/src/test/org/apache/solr/request/TestFacetMethods.java
index 1da41f5..ea7c001 100644
--- a/solr/core/src/test/org/apache/solr/request/TestFacetMethods.java
+++ b/solr/core/src/test/org/apache/solr/request/TestFacetMethods.java
@@ -17,7 +17,9 @@
 
 package org.apache.solr.request;
 
+import org.apache.solr.request.SimpleFacets.FacetMethod;
 import org.apache.solr.schema.BoolField;
+import org.apache.solr.schema.IntPointField;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.StrField;
 import org.apache.solr.schema.TrieIntField;
@@ -203,5 +205,15 @@ public class TestFacetMethods {
     assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, null, 1));
 
   }
+  
+  @Test
+  public void testPointFields() {
+    // Methods other than FCS are not currently supported for PointFields
+    SchemaField field = new SchemaField("foo", new IntPointField());
+    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, null, 0));
+    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, 0));
+    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, 0));
+    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, 0));
+  }
 
 }


[36/38] lucene-solr:jira/solr-9857: LUCENE-7640: Fix test.

Posted by ab...@apache.org.
LUCENE-7640: Fix test.


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

Branch: refs/heads/jira/solr-9857
Commit: 85a05b546bee9ff7484372c44854d4fd66d63b36
Parents: e8fa599
Author: Adrien Grand <jp...@gmail.com>
Authored: Thu Jan 19 09:54:23 2017 +0100
Committer: Adrien Grand <jp...@gmail.com>
Committed: Thu Jan 19 09:54:50 2017 +0100

----------------------------------------------------------------------
 .../lucene/codecs/lucene60/TestLucene60PointsFormat.java  | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/85a05b54/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java b/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
index 4287273..08dc6c6 100644
--- a/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
+++ b/lucene/core/src/test/org/apache/lucene/codecs/lucene60/TestLucene60PointsFormat.java
@@ -31,7 +31,9 @@ import org.apache.lucene.index.BasePointsFormatTestCase;
 import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.MockRandomMergePolicy;
 import org.apache.lucene.index.PointValues;
 import org.apache.lucene.index.SegmentReadState;
 import org.apache.lucene.index.SegmentWriteState;
@@ -97,7 +99,13 @@ public class TestLucene60PointsFormat extends BasePointsFormatTestCase {
 
   public void testEstimatePointCount() throws IOException {
     Directory dir = newDirectory();
-    IndexWriter w = new IndexWriter(dir, newIndexWriterConfig());
+    IndexWriterConfig iwc = newIndexWriterConfig();
+    // Avoid mockRandomMP since it may cause non-optimal merges that make the
+    // number of points per leaf hard to predict
+    while (iwc.getMergePolicy() instanceof MockRandomMergePolicy) {
+      iwc.setMergePolicy(newMergePolicy());
+    }
+    IndexWriter w = new IndexWriter(dir, iwc);
     byte[] pointValue = new byte[3];
     byte[] uniquePointValue = new byte[3];
     random().nextBytes(uniquePointValue);


[26/38] lucene-solr:jira/solr-9857: SOLR-9836: Add ability to recover from leader when index corruption is detected on SolrCore creation.

Posted by ab...@apache.org.
SOLR-9836: Add ability to recover from leader when index corruption is detected on SolrCore creation.


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

Branch: refs/heads/jira/solr-9857
Commit: a89560bb72de57d291db45c52c04b9edf6c91d92
Parents: a37bfa7
Author: markrmiller <ma...@apache.org>
Authored: Wed Jan 18 15:17:06 2017 -0500
Committer: markrmiller <ma...@apache.org>
Committed: Wed Jan 18 19:45:22 2017 -0500

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   6 +
 .../org/apache/solr/core/CoreContainer.java     | 109 +++++++++++++++-
 .../org/apache/solr/core/DirectoryFactory.java  |  27 ++++
 .../src/java/org/apache/solr/core/SolrCore.java | 120 ++++++++++++++----
 .../org/apache/solr/handler/IndexFetcher.java   |  60 +--------
 .../org/apache/solr/handler/RestoreCore.java    |   2 +-
 .../solr/cloud/MissingSegmentRecoveryTest.java  | 123 +++++++++++++++++++
 7 files changed, 364 insertions(+), 83 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a89560bb/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 55ab04d..205c7bc 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -90,6 +90,12 @@ Jetty 9.3.14.v20161028
 Detailed Change List
 ----------------------
 
+New Features
+----------------------
+
+* SOLR-9836: Add ability to recover from leader when index corruption is detected on SolrCore creation.
+  (Mike Drob via Mark Miller)
+
 Bug Fixes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a89560bb/solr/core/src/java/org/apache/solr/core/CoreContainer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 3c4ed56..023e7b1 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -20,9 +20,12 @@ import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -39,22 +42,29 @@ import com.google.common.collect.Maps;
 import org.apache.http.auth.AuthSchemeProvider;
 import org.apache.http.client.CredentialsProvider;
 import org.apache.http.config.Lookup;
+import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.store.Directory;
 import org.apache.solr.client.solrj.impl.HttpClientUtil;
 import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder;
 import org.apache.solr.client.solrj.impl.SolrHttpClientContextBuilder;
 import org.apache.solr.client.solrj.impl.SolrHttpClientContextBuilder.AuthSchemeRegistryProvider;
 import org.apache.solr.client.solrj.impl.SolrHttpClientContextBuilder.CredentialsProviderProvider;
 import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
+import org.apache.solr.cloud.CloudDescriptor;
 import org.apache.solr.cloud.Overseer;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.cloud.Replica.State;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.DirectoryFactory.DirContext;
 import org.apache.solr.core.backup.repository.BackupRepository;
 import org.apache.solr.core.backup.repository.BackupRepositoryFactory;
 import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.handler.SnapShooter;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.handler.admin.ConfigSetsHandler;
 import org.apache.solr.handler.admin.CoreAdminHandler;
@@ -166,6 +176,8 @@ public class CoreContainer {
 
   protected MetricsHandler metricsHandler;
 
+  private enum CoreInitFailedAction { fromleader, none }
+
   /**
    * This method instantiates a new instance of {@linkplain BackupRepository}.
    *
@@ -911,7 +923,11 @@ public class CoreContainer {
 
       ConfigSet coreConfig = coreConfigService.getConfig(dcore);
       log.info("Creating SolrCore '{}' using configuration from {}", dcore.getName(), coreConfig.getName());
-      core = new SolrCore(dcore, coreConfig);
+      try {
+        core = new SolrCore(dcore, coreConfig);
+      } catch (SolrException e) {
+        core = processCoreCreateException(e, dcore, coreConfig);
+      }
 
       // always kick off recovery if we are in non-Cloud mode
       if (!isZooKeeperAware() && core.getUpdateHandler().getUpdateLog() != null) {
@@ -923,14 +939,12 @@ public class CoreContainer {
       return core;
     } catch (Exception e) {
       coreInitFailures.put(dcore.getName(), new CoreLoadFailure(dcore, e));
-      log.error("Error creating core [{}]: {}", dcore.getName(), e.getMessage(), e);
       final SolrException solrException = new SolrException(ErrorCode.SERVER_ERROR, "Unable to create core [" + dcore.getName() + "]", e);
       if(core != null && !core.isClosed())
         IOUtils.closeQuietly(core);
       throw solrException;
     } catch (Throwable t) {
       SolrException e = new SolrException(ErrorCode.SERVER_ERROR, "JVM Error creating core [" + dcore.getName() + "]: " + t.getMessage(), t);
-      log.error("Error creating core [{}]: {}", dcore.getName(), t.getMessage(), t);
       coreInitFailures.put(dcore.getName(), new CoreLoadFailure(dcore, e));
       if(core != null && !core.isClosed())
         IOUtils.closeQuietly(core);
@@ -938,7 +952,96 @@ public class CoreContainer {
     } finally {
       MDCLoggingContext.clear();
     }
+  }
+  
+  /**
+   * Take action when we failed to create a SolrCore. If error is due to corrupt index, try to recover. Various recovery
+   * strategies can be specified via system properties "-DCoreInitFailedAction={fromleader, none}"
+   *
+   * @see CoreInitFailedAction
+   *
+   * @param original
+   *          the problem seen when loading the core the first time.
+   * @param dcore
+   *          core descriptor for the core to create
+   * @param coreConfig
+   *          core config for the core to create
+   * @return if possible
+   * @throws SolrException
+   *           rethrows the original exception if we will not attempt to recover, throws a new SolrException with the
+   *           original exception as a suppressed exception if there is a second problem creating the solr core.
+   */
+  private SolrCore processCoreCreateException(SolrException original, CoreDescriptor dcore, ConfigSet coreConfig) {
+    // Traverse full chain since CIE may not be root exception
+    Throwable cause = original;
+    while ((cause = cause.getCause()) != null) {
+      if (cause instanceof CorruptIndexException) {
+        break;
+      }
+    }
+    
+    // If no CorruptIndexExeption, nothing we can try here
+    if (cause == null) throw original;
+    
+    CoreInitFailedAction action = CoreInitFailedAction.valueOf(System.getProperty(CoreInitFailedAction.class.getSimpleName(), "none"));
+    log.debug("CorruptIndexException while creating core, will attempt to repair via {}", action);
+    
+    switch (action) {
+      case fromleader: // Recovery from leader on a CorruptedIndexException
+        if (isZooKeeperAware()) {
+          CloudDescriptor desc = dcore.getCloudDescriptor();
+          try {
+            Replica leader = getZkController().getClusterState()
+                .getCollection(desc.getCollectionName())
+                .getSlice(desc.getShardId())
+                .getLeader();
+            if (leader != null && leader.getState() == State.ACTIVE) {
+              log.info("Found active leader, will attempt to create fresh core and recover.");
+              resetIndexDirectory(dcore, coreConfig);
+              return new SolrCore(dcore, coreConfig);
+            }
+          } catch (SolrException se) {
+            se.addSuppressed(original);
+            throw se;
+          }
+        }
+        throw original;
+      case none:
+        throw original;
+      default:
+        log.warn("Failed to create core, and did not recognize specified 'CoreInitFailedAction': [{}]. Valid options are {}.",
+            action, Arrays.asList(CoreInitFailedAction.values()));
+        throw original;
+    }
+  }
+
+  /**
+   * Write a new index directory for the a SolrCore, but do so without loading it.
+   */
+  private void resetIndexDirectory(CoreDescriptor dcore, ConfigSet coreConfig) {
+    SolrConfig config = coreConfig.getSolrConfig();
+
+    String registryName = SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, dcore.getName());
+    DirectoryFactory df = DirectoryFactory.loadDirectoryFactory(config, this, registryName);
+    String dataDir = SolrCore.findDataDir(df, null, config, dcore);
 
+    String tmpIdxDirName = "index." + new SimpleDateFormat(SnapShooter.DATE_FMT, Locale.ROOT).format(new Date());
+    SolrCore.modifyIndexProps(df, dataDir, config, tmpIdxDirName);
+
+    // Free the directory object that we had to create for this
+    Directory dir = null;
+    try {
+      dir = df.get(dataDir, DirContext.META_DATA, config.indexConfig.lockType);
+    } catch (IOException e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+    } finally {
+      try {
+        df.release(dir);
+        df.doneWithDirectory(dir);
+      } catch (IOException e) {
+        SolrException.log(log, e);
+      }
+    }
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a89560bb/solr/core/src/java/org/apache/solr/core/DirectoryFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/DirectoryFactory.java b/solr/core/src/java/org/apache/solr/core/DirectoryFactory.java
index ac18d7e..9dd0d8a 100644
--- a/solr/core/src/java/org/apache/solr/core/DirectoryFactory.java
+++ b/solr/core/src/java/org/apache/solr/core/DirectoryFactory.java
@@ -383,4 +383,31 @@ public abstract class DirectoryFactory implements NamedListInitializedPlugin,
     
     return baseDir;
   }
+
+  /**
+   * Create a new DirectoryFactory instance from the given SolrConfig and tied to the specified core container.
+   */
+  static DirectoryFactory loadDirectoryFactory(SolrConfig config, CoreContainer cc, String registryName) {
+    final PluginInfo info = config.getPluginInfo(DirectoryFactory.class.getName());
+    final DirectoryFactory dirFactory;
+    if (info != null) {
+      log.debug(info.className);
+      dirFactory = config.getResourceLoader().newInstance(info.className, DirectoryFactory.class);
+      // allow DirectoryFactory instances to access the CoreContainer
+      dirFactory.initCoreContainer(cc);
+      dirFactory.init(info.initArgs);
+    } else {
+      log.debug("solr.NRTCachingDirectoryFactory");
+      dirFactory = new NRTCachingDirectoryFactory();
+      dirFactory.initCoreContainer(cc);
+    }
+    if (config.indexConfig.metricsInfo != null && config.indexConfig.metricsInfo.isEnabled()) {
+      final DirectoryFactory factory = new MetricsDirectoryFactory(cc.getMetricManager(),
+          registryName, dirFactory);
+        factory.init(config.indexConfig.metricsInfo.initArgs);
+      return factory;
+    } else {
+      return dirFactory;
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a89560bb/solr/core/src/java/org/apache/solr/core/SolrCore.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index 697e008..74238e7 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -23,6 +23,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
 import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Constructor;
 import java.net.URL;
@@ -67,6 +69,7 @@ import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.IOContext;
 import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
 import org.apache.lucene.store.LockObtainFailedException;
 import org.apache.solr.client.solrj.impl.BinaryResponseParser;
 import org.apache.solr.cloud.CloudDescriptor;
@@ -148,6 +151,7 @@ import org.apache.solr.update.processor.UpdateRequestProcessorFactory;
 import org.apache.solr.util.DefaultSolrThreadFactory;
 import org.apache.solr.util.NumberUtils;
 import org.apache.solr.util.PropertiesInputStream;
+import org.apache.solr.util.PropertiesOutputStream;
 import org.apache.solr.util.RefCounted;
 import org.apache.solr.util.plugin.NamedListInitializedPlugin;
 import org.apache.solr.util.plugin.PluginInfoInitialized;
@@ -646,27 +650,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
   }
 
   private DirectoryFactory initDirectoryFactory() {
-    final PluginInfo info = solrConfig.getPluginInfo(DirectoryFactory.class.getName());
-    final DirectoryFactory dirFactory;
-    if (info != null) {
-      log.debug(info.className);
-      dirFactory = getResourceLoader().newInstance(info.className, DirectoryFactory.class);
-      // allow DirectoryFactory instances to access the CoreContainer
-      dirFactory.initCoreContainer(getCoreDescriptor().getCoreContainer());
-      dirFactory.init(info.initArgs);
-    } else {
-      log.debug("solr.NRTCachingDirectoryFactory");
-      dirFactory = new NRTCachingDirectoryFactory();
-      dirFactory.initCoreContainer(getCoreDescriptor().getCoreContainer());
-    }
-    if (solrConfig.indexConfig.metricsInfo != null && solrConfig.indexConfig.metricsInfo.isEnabled()) {
-      final DirectoryFactory factory = new MetricsDirectoryFactory(coreDescriptor.getCoreContainer().getMetricManager(),
-          coreMetricManager.getRegistryName(), dirFactory);
-        factory.init(solrConfig.indexConfig.metricsInfo.initArgs);
-      return factory;
-    } else {
-      return dirFactory;
-    }
+    return DirectoryFactory.loadDirectoryFactory(solrConfig, getCoreDescriptor().getCoreContainer(), coreMetricManager.getRegistryName());
   }
 
   private void initIndexReaderFactory() {
@@ -1145,6 +1129,26 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
   }
 
   private String initDataDir(String dataDir, SolrConfig config, CoreDescriptor coreDescriptor) {
+    return findDataDir(getDirectoryFactory(), dataDir, config, coreDescriptor);
+  }
+
+  /**
+   * Locate the data directory for a given config and core descriptor.
+   *
+   * @param directoryFactory
+   *          The directory factory to use if necessary to calculate an absolute path. Should be the same as what will
+   *          be used to open the data directory later.
+   * @param dataDir
+   *          An optional hint to the data directory location. Will be normalized and used if not null.
+   * @param config
+   *          A solr config to retrieve the default data directory location, if used.
+   * @param coreDescriptor
+   *          descriptor to load the actual data dir from, if not using the defualt.
+   * @return a normalized data directory name
+   * @throws SolrException
+   *           if the data directory cannot be loaded from the core descriptor
+   */
+  static String findDataDir(DirectoryFactory directoryFactory, String dataDir, SolrConfig config, CoreDescriptor coreDescriptor) {
     if (dataDir == null) {
       if (coreDescriptor.usingDefaultDataDir()) {
         dataDir = config.getDataDir();
@@ -1163,6 +1167,80 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
     return SolrResourceLoader.normalizeDir(dataDir);
   }
 
+
+  public boolean modifyIndexProps(String tmpIdxDirName) {
+    return SolrCore.modifyIndexProps(getDirectoryFactory(), getDataDir(), getSolrConfig(), tmpIdxDirName);
+  }
+  
+  /**
+   * Update the index.properties file with the new index sub directory name
+   */
+  // package private
+  static boolean modifyIndexProps(DirectoryFactory directoryFactory, String dataDir, SolrConfig solrConfig, String tmpIdxDirName) {
+    log.info("Updating index properties... index="+tmpIdxDirName);
+    Directory dir = null;
+    try {
+      dir = directoryFactory.get(dataDir, DirContext.META_DATA, solrConfig.indexConfig.lockType);
+      String tmpIdxPropName = IndexFetcher.INDEX_PROPERTIES + "." + System.nanoTime();
+      writeNewIndexProps(dir, tmpIdxPropName, tmpIdxDirName);
+      directoryFactory.renameWithOverwrite(dir, tmpIdxPropName, IndexFetcher.INDEX_PROPERTIES);
+      return true;
+    } catch (IOException e1) {
+      throw new RuntimeException(e1);
+    } finally {
+      if (dir != null) {
+        try {
+          directoryFactory.release(dir);
+        } catch (IOException e) {
+          SolrException.log(log, "", e);
+        }
+      }
+    }
+  }
+  
+  /**
+   * Write the index.properties file with the new index sub directory name
+   * @param dir a data directory (containing an index.properties file)
+   * @param tmpFileName the file name to write the new index.properties to
+   * @param tmpIdxDirName new index directory name
+   */
+  private static void writeNewIndexProps(Directory dir, String tmpFileName, String tmpIdxDirName) {
+    if (tmpFileName == null) {
+      tmpFileName = IndexFetcher.INDEX_PROPERTIES;
+    }
+    final Properties p = new Properties();
+    
+    // Read existing properties
+    try {
+      final IndexInput input = dir.openInput(IndexFetcher.INDEX_PROPERTIES, DirectoryFactory.IOCONTEXT_NO_CACHE);
+      final InputStream is = new PropertiesInputStream(input);
+      try {
+        p.load(new InputStreamReader(is, StandardCharsets.UTF_8));
+      } catch (Exception e) {
+        log.error("Unable to load " + IndexFetcher.INDEX_PROPERTIES, e);
+      } finally {
+        IOUtils.closeQuietly(is);
+      }
+    } catch (IOException e) {
+      // ignore; file does not exist
+    }
+    
+    p.put("index", tmpIdxDirName);
+
+    // Write new properties
+    Writer os = null;
+    try {
+      IndexOutput out = dir.createOutput(tmpFileName, DirectoryFactory.IOCONTEXT_NO_CACHE);
+      os = new OutputStreamWriter(new PropertiesOutputStream(out), StandardCharsets.UTF_8);
+      p.store(os, IndexFetcher.INDEX_PROPERTIES);
+      dir.sync(Collections.singleton(tmpFileName));
+    } catch (Exception e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to write " + IndexFetcher.INDEX_PROPERTIES, e);
+    } finally {
+      IOUtils.closeQuietly(os);
+    }
+  }
+
   private String initUpdateLogDir(CoreDescriptor coreDescriptor) {
     String updateLogDir = coreDescriptor.getUlogDir();
     if (updateLogDir == null) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a89560bb/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java b/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
index 8bdd2b8..968af61 100644
--- a/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
+++ b/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
@@ -21,7 +21,6 @@ import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.lang.invoke.MethodHandles;
@@ -92,7 +91,6 @@ import org.apache.solr.update.UpdateLog;
 import org.apache.solr.update.VersionInfo;
 import org.apache.solr.util.DefaultSolrThreadFactory;
 import org.apache.solr.util.FileUtils;
-import org.apache.solr.util.PropertiesInputStream;
 import org.apache.solr.util.PropertiesOutputStream;
 import org.apache.solr.util.RTimer;
 import org.apache.solr.util.RefCounted;
@@ -460,7 +458,7 @@ public class IndexFetcher {
             reloadCore = true;
             downloadConfFiles(confFilesToDownload, latestGeneration);
             if (isFullCopyNeeded) {
-              successfulInstall = IndexFetcher.modifyIndexProps(solrCore, tmpIdxDirName);
+              successfulInstall = solrCore.modifyIndexProps(tmpIdxDirName);
               deleteTmpIdxDir = false;
             } else {
               successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
@@ -488,7 +486,7 @@ public class IndexFetcher {
           } else {
             terminateAndWaitFsyncService();
             if (isFullCopyNeeded) {
-              successfulInstall = IndexFetcher.modifyIndexProps(solrCore, tmpIdxDirName);
+              successfulInstall = solrCore.modifyIndexProps(tmpIdxDirName);
               deleteTmpIdxDir = false;
             } else {
               successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
@@ -1189,60 +1187,6 @@ public class IndexFetcher {
     return new SimpleDateFormat(SnapShooter.DATE_FMT, Locale.ROOT).format(d);
   }
 
-  /**
-   * If the index is stale by any chance, load index from a different dir in the data dir.
-   */
-  protected static boolean modifyIndexProps(SolrCore solrCore, String tmpIdxDirName) {
-    LOG.info("New index installed. Updating index properties... index="+tmpIdxDirName);
-    Properties p = new Properties();
-    Directory dir = null;
-    try {
-      dir = solrCore.getDirectoryFactory().get(solrCore.getDataDir(), DirContext.META_DATA, solrCore.getSolrConfig().indexConfig.lockType);
-      if (slowFileExists(dir, IndexFetcher.INDEX_PROPERTIES)){
-        final IndexInput input = dir.openInput(IndexFetcher.INDEX_PROPERTIES, DirectoryFactory.IOCONTEXT_NO_CACHE);
-
-        final InputStream is = new PropertiesInputStream(input);
-        try {
-          p.load(new InputStreamReader(is, StandardCharsets.UTF_8));
-        } catch (Exception e) {
-          LOG.error("Unable to load " + IndexFetcher.INDEX_PROPERTIES, e);
-        } finally {
-          IOUtils.closeQuietly(is);
-        }
-      }
-
-      String tmpFileName = IndexFetcher.INDEX_PROPERTIES + "." + System.nanoTime();
-      final IndexOutput out = dir.createOutput(tmpFileName, DirectoryFactory.IOCONTEXT_NO_CACHE);
-      p.put("index", tmpIdxDirName);
-      Writer os = null;
-      try {
-        os = new OutputStreamWriter(new PropertiesOutputStream(out), StandardCharsets.UTF_8);
-        p.store(os, tmpFileName);
-        dir.sync(Collections.singleton(tmpFileName));
-      } catch (Exception e) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
-            "Unable to write " + IndexFetcher.INDEX_PROPERTIES, e);
-      } finally {
-        IOUtils.closeQuietly(os);
-      }
-      
-      solrCore.getDirectoryFactory().renameWithOverwrite(dir, tmpFileName, IndexFetcher.INDEX_PROPERTIES);
-      return true;
-
-    } catch (IOException e1) {
-      throw new RuntimeException(e1);
-    } finally {
-      if (dir != null) {
-        try {
-          solrCore.getDirectoryFactory().release(dir);
-        } catch (IOException e) {
-          SolrException.log(LOG, "", e);
-        }
-      }
-    }
-
-  }
-
   private final Map<String, FileInfo> confFileInfoCache = new HashMap<>();
 
   /**

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a89560bb/solr/core/src/java/org/apache/solr/handler/RestoreCore.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/RestoreCore.java b/solr/core/src/java/org/apache/solr/handler/RestoreCore.java
index c00d7bd..e750631 100644
--- a/solr/core/src/java/org/apache/solr/handler/RestoreCore.java
+++ b/solr/core/src/java/org/apache/solr/handler/RestoreCore.java
@@ -101,7 +101,7 @@ public class RestoreCore implements Callable<Boolean> {
         }
       }
       log.debug("Switching directories");
-      IndexFetcher.modifyIndexProps(core, restoreIndexName);
+      core.modifyIndexProps(restoreIndexName);
 
       boolean success;
       try {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a89560bb/solr/core/src/test/org/apache/solr/cloud/MissingSegmentRecoveryTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/MissingSegmentRecoveryTest.java b/solr/core/src/test/org/apache/solr/cloud/MissingSegmentRecoveryTest.java
new file mode 100644
index 0000000..a7bfa20
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/MissingSegmentRecoveryTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.cloud;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.util.LuceneTestCase.Slow;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.cloud.DocCollection;
+import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.core.SolrCore;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+@Slow
+public class MissingSegmentRecoveryTest extends SolrCloudTestCase {
+  final String collection = getClass().getSimpleName();
+  
+  Replica leader;
+  Replica replica;
+
+  @BeforeClass
+  public static void setupCluster() throws Exception {
+    configureCluster(2)
+        .addConfig("conf", configset("cloud-minimal"))
+        .configure();
+    useFactory("solr.StandardDirectoryFactory");
+  }
+
+  @Before
+  public void setup() throws SolrServerException, IOException {
+    CollectionAdminRequest.createCollection(collection, "conf", 1, 2)
+        .setMaxShardsPerNode(1)
+        .process(cluster.getSolrClient());
+    waitForState("Expected a collection with one shard and two replicas", collection, clusterShape(1, 2));
+    cluster.getSolrClient().setDefaultCollection(collection);
+
+    List<SolrInputDocument> docs = new ArrayList<>();
+    for (int i = 0; i < 10; i++) {
+      SolrInputDocument doc = new SolrInputDocument();
+      doc.addField("id", i);
+      docs.add(doc);
+    }
+
+    cluster.getSolrClient().add(docs);
+    cluster.getSolrClient().commit();
+    
+    DocCollection state = getCollectionState(collection);
+    leader = state.getLeader("shard1");
+    replica = getRandomReplica(state.getSlice("shard1"), (r) -> leader != r);
+  }
+  
+  @After
+  public void teardown() throws Exception {
+    System.clearProperty("CoreInitFailedAction");
+    CollectionAdminRequest.deleteCollection(collection).process(cluster.getSolrClient());
+  }
+
+  @AfterClass
+  public static void teardownCluster() throws Exception {
+    resetFactory();
+  }
+
+  @Test
+  public void testLeaderRecovery() throws Exception {
+    System.setProperty("CoreInitFailedAction", "fromleader");
+
+    // Simulate failure by truncating the segment_* files
+    for (File segment : getSegmentFiles(replica)) {
+      truncate(segment);
+    }
+
+    // Might not need a sledge-hammer to reload the core
+    JettySolrRunner jetty = cluster.getReplicaJetty(replica);
+    jetty.stop();
+    jetty.start();
+
+    waitForState("Expected a collection with one shard and two replicas", collection, clusterShape(1, 2));
+    
+    QueryResponse resp = cluster.getSolrClient().query(collection, new SolrQuery("*:*"));
+    assertEquals(10, resp.getResults().getNumFound());
+  }
+
+  private File[] getSegmentFiles(Replica replica) {
+    try (SolrCore core = cluster.getReplicaJetty(replica).getCoreContainer().getCore(replica.getCoreName())) {
+      File indexDir = new File(core.getDataDir(), "index");
+      return indexDir.listFiles((File dir, String name) -> {
+        return name.startsWith("segments_");
+      });
+    }
+  }
+  
+  private void truncate(File file) throws IOException {
+    Files.write(file.toPath(), new byte[0], StandardOpenOption.TRUNCATE_EXISTING);
+  }
+}


[25/38] lucene-solr:jira/solr-9857: SOLR-9980: Expose configVersion in core admin status

Posted by ab...@apache.org.
SOLR-9980: Expose configVersion in core admin status


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

Branch: refs/heads/jira/solr-9857
Commit: a37bfa75a0d1da3b8f513528f1b16acd4b3f0f0b
Parents: 85061e3
Author: Tomas Fernandez Lobbe <tf...@apache.org>
Authored: Wed Jan 18 15:01:27 2017 -0800
Committer: Tomas Fernandez Lobbe <tf...@apache.org>
Committed: Wed Jan 18 15:01:27 2017 -0800

----------------------------------------------------------------------
 solr/CHANGES.txt                                                 | 4 ++++
 .../java/org/apache/solr/handler/admin/CoreAdminOperation.java   | 1 +
 2 files changed, 5 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a37bfa75/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 1f32c24..55ab04d 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -106,6 +106,10 @@ Optimizations
 * SOLR-9941: Clear the deletes lists at UpdateLog before replaying from log. This prevents redundantly pre-applying
   DBQs, during the log replay, to every update in the log as if the DBQs were out of order. (hossman, Ishan Chattopadhyaya)
 
+Other Changes
+----------------------
+* SOLR-9980: Expose configVersion in core admin status (Jessica Cheng Mallet via Tom�s Fern�ndez L�bbe)
+
 ==================  6.4.0 ==================
 
 Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a37bfa75/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java
index 5836ed3..af3af4d 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java
@@ -335,6 +335,7 @@ enum CoreAdminOperation implements CoreAdminOp {
           info.add("uptime", core.getUptimeMs());
           if (cores.isZooKeeperAware()) {
             info.add("lastPublished", core.getCoreDescriptor().getCloudDescriptor().getLastPublished().toString().toLowerCase(Locale.ROOT));
+            info.add("configVersion", core.getSolrConfig().getZnodeVersion());
           }
           if (isIndexInfoNeeded) {
             RefCounted<SolrIndexSearcher> searcher = core.getSearcher();