lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mikemcc...@apache.org
Subject [02/10] lucene-solr:master: LUCENE-7283: move SlowCompositeReaderWrapper and uninverting to solr
Date Wed, 25 May 2016 13:21:12 GMT
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/baa5344e/solr/core/src/test/org/apache/solr/uninverting/TestFieldCacheSort.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/uninverting/TestFieldCacheSort.java b/solr/core/src/test/org/apache/solr/uninverting/TestFieldCacheSort.java
new file mode 100644
index 0000000..34d9237
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/uninverting/TestFieldCacheSort.java
@@ -0,0 +1,1814 @@
+/*
+ * 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.uninverting;
+
+import java.io.IOException;
+import java.util.Collections;
+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.DoublePoint;
+import org.apache.lucene.document.LegacyDoubleField;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FloatPoint;
+import org.apache.lucene.document.IntPoint;
+import org.apache.lucene.document.LegacyFloatField;
+import org.apache.lucene.document.LegacyIntField;
+import org.apache.lucene.document.LegacyLongField;
+import org.apache.lucene.document.LongPoint;
+import org.apache.lucene.document.StoredField;
+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.IndexWriterConfig;
+import org.apache.lucene.index.MultiReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
+import org.apache.solr.uninverting.UninvertingReader.Type;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+
+/*
+ * Tests sorting (but with fieldcache instead of docvalues)
+ */
+public class TestFieldCacheSort extends LuceneTestCase {
+
+  public void testString() throws IOException {
+    testString(SortField.Type.STRING);
+  }
+
+  public void testStringVal() throws Exception {
+    testString(SortField.Type.STRING_VAL);
+  }
+
+  /** Tests sorting on type string */
+  private void testString(SortField.Type sortType) throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "bar", Field.Store.YES));
+    writer.addDocument(doc);
+    Type type = sortType == SortField.Type.STRING ? Type.SORTED : Type.BINARY;
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", type));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", sortType));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(2, td.totalHits);
+    // 'bar' comes before 'foo'
+    assertEquals("bar", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("foo", searcher.doc(td.scoreDocs[1].doc).get("value"));
+
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  public void testStringMissing() throws IOException {
+    testStringMissing(SortField.Type.STRING);
+  }
+  
+  public void testStringValMissing() throws IOException {
+    testStringMissing(SortField.Type.STRING_VAL);
+  }
+  
+  /** Tests sorting on type string with a missing value */
+  private void testStringMissing(SortField.Type sortType) throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "bar", Field.Store.YES));
+    writer.addDocument(doc);
+    Type type = sortType == SortField.Type.STRING ? Type.SORTED : Type.BINARY;
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", type));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", sortType));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null comes first
+    assertNull(searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("bar", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("foo", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  public void testStringReverse() throws IOException {
+    testStringReverse(SortField.Type.STRING);
+  }
+  
+  public void testStringValReverse() throws IOException {
+    testStringReverse(SortField.Type.STRING_VAL);
+  }
+  
+  /** Tests reverse sorting on type string */
+  private void testStringReverse(SortField.Type sortType) throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newStringField("value", "bar", Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.YES));
+    writer.addDocument(doc);
+    Type type = sortType == SortField.Type.STRING ? Type.SORTED : Type.BINARY;
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", type));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", sortType, true));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(2, td.totalHits);
+    // 'foo' comes after 'bar' in reverse order
+    assertEquals("foo", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("bar", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  public void testStringMissingSortedFirst() throws IOException {
+    testStringMissingSortedFirst(SortField.Type.STRING);
+  }
+  
+  public void testStringValMissingSortedFirst() throws IOException {
+    testStringMissingSortedFirst(SortField.Type.STRING_VAL);
+  }
+
+  /** Tests sorting on type string with a missing
+   *  value sorted first */
+  private void testStringMissingSortedFirst(SortField.Type sortType) throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "bar", Field.Store.YES));
+    writer.addDocument(doc);
+    Type type = sortType == SortField.Type.STRING ? Type.SORTED : Type.BINARY;
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", type));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    SortField sf = new SortField("value", sortType);
+    Sort sort = new Sort(sf);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null comes first
+    assertNull(searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("bar", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("foo", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+
+  public void testStringMissingSortedFirstReverse() throws IOException {
+    testStringMissingSortedFirstReverse(SortField.Type.STRING);
+  }
+  
+  public void testStringValMissingSortedFirstReverse() throws IOException {
+    testStringMissingSortedFirstReverse(SortField.Type.STRING_VAL);
+  }
+  
+  /** Tests reverse sorting on type string with a missing
+   *  value sorted first */
+  private void testStringMissingSortedFirstReverse(SortField.Type sortType) throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "bar", Field.Store.YES));
+    writer.addDocument(doc);
+    Type type = sortType == SortField.Type.STRING ? Type.SORTED : Type.BINARY;
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", type));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    SortField sf = new SortField("value", sortType, true);
+    Sort sort = new Sort(sf);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    assertEquals("foo", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("bar", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    // null comes last
+    assertNull(searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+
+  public void testStringMissingSortedLast() throws IOException {
+    testStringMissingSortedLast(SortField.Type.STRING);
+  }
+  
+  public void testStringValMissingSortedLast() throws IOException {
+    testStringMissingSortedLast(SortField.Type.STRING_VAL);
+  }
+
+  /** Tests sorting on type string with a missing
+   *  value sorted last */
+  private void testStringMissingSortedLast(SortField.Type sortType) throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "bar", Field.Store.YES));
+    writer.addDocument(doc);
+    Type type = sortType == SortField.Type.STRING ? Type.SORTED : Type.BINARY;
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", type));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    SortField sf = new SortField("value", sortType);
+    sf.setMissingValue(SortField.STRING_LAST);
+    Sort sort = new Sort(sf);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    assertEquals("bar", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("foo", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    // null comes last
+    assertNull(searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+
+  public void testStringMissingSortedLastReverse() throws IOException {
+    testStringMissingSortedLastReverse(SortField.Type.STRING);
+  }
+  
+  public void testStringValMissingSortedLastReverse() throws IOException {
+    testStringMissingSortedLastReverse(SortField.Type.STRING_VAL);
+  }
+
+  /** Tests reverse sorting on type string with a missing
+   *  value sorted last */
+  private void testStringMissingSortedLastReverse(SortField.Type sortType) throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "bar", Field.Store.YES));
+    writer.addDocument(doc);
+    Type type = sortType == SortField.Type.STRING ? Type.SORTED : Type.BINARY;
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", type));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    SortField sf = new SortField("value", sortType, true);
+    sf.setMissingValue(SortField.STRING_LAST);
+    Sort sort = new Sort(sf);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null comes first
+    assertNull(searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("foo", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("bar", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on internal docid order */
+  public void testFieldDoc() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.NO));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "bar", Field.Store.NO));
+    writer.addDocument(doc);
+    IndexReader ir = writer.getReader();
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(SortField.FIELD_DOC);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(2, td.totalHits);
+    // docid 0, then docid 1
+    assertEquals(0, td.scoreDocs[0].doc);
+    assertEquals(1, td.scoreDocs[1].doc);
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on reverse internal docid order */
+  public void testFieldDocReverse() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.NO));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "bar", Field.Store.NO));
+    writer.addDocument(doc);
+    IndexReader ir = writer.getReader();
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField(null, SortField.Type.DOC, true));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(2, td.totalHits);
+    // docid 1, then docid 0
+    assertEquals(1, td.scoreDocs[0].doc);
+    assertEquals(0, td.scoreDocs[1].doc);
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests default sort (by score) */
+  public void testFieldScore() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newTextField("value", "foo bar bar bar bar", Field.Store.NO));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newTextField("value", "foo foo foo foo foo", Field.Store.NO));
+    writer.addDocument(doc);
+    IndexReader ir = writer.getReader();
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort();
+
+    TopDocs actual = searcher.search(new TermQuery(new Term("value", "foo")), 10, sort);
+    assertEquals(2, actual.totalHits);
+
+    TopDocs expected = searcher.search(new TermQuery(new Term("value", "foo")), 10);
+    // the two topdocs should be the same
+    assertEquals(expected.totalHits, actual.totalHits);
+    for (int i = 0; i < actual.scoreDocs.length; i++) {
+      assertEquals(actual.scoreDocs[i].doc, expected.scoreDocs[i].doc);
+    }
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests default sort (by score) in reverse */
+  public void testFieldScoreReverse() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newTextField("value", "foo bar bar bar bar", Field.Store.NO));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newTextField("value", "foo foo foo foo foo", Field.Store.NO));
+    writer.addDocument(doc);
+    IndexReader ir = writer.getReader();
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField(null, SortField.Type.SCORE, true));
+
+    TopDocs actual = searcher.search(new TermQuery(new Term("value", "foo")), 10, sort);
+    assertEquals(2, actual.totalHits);
+
+    TopDocs expected = searcher.search(new TermQuery(new Term("value", "foo")), 10);
+    // the two topdocs should be the reverse of each other
+    assertEquals(expected.totalHits, actual.totalHits);
+    assertEquals(actual.scoreDocs[0].doc, expected.scoreDocs[1].doc);
+    assertEquals(actual.scoreDocs[1].doc, expected.scoreDocs[0].doc);
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+
+  /** Tests sorting on type int */
+  public void testInt() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new IntPoint("value", 300000));
+    doc.add(new StoredField("value", 300000));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new IntPoint("value", -1));
+    doc.add(new StoredField("value", -1));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new IntPoint("value", 4));
+    doc.add(new StoredField("value", 4));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.INTEGER_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.INT));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // numeric order
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("300000", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type int with a missing value */
+  public void testIntMissing() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new IntPoint("value", -1));
+    doc.add(new StoredField("value", -1));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new IntPoint("value", 4));
+    doc.add(new StoredField("value", 4));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.INTEGER_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.INT));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as a 0
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type int, specifying the missing value should be treated as Integer.MAX_VALUE */
+  public void testIntMissingLast() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new IntPoint("value", -1));
+    doc.add(new StoredField("value", -1));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new IntPoint("value", 4));
+    doc.add(new StoredField("value", 4));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.INTEGER_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    SortField sortField = new SortField("value", SortField.Type.INT);
+    sortField.setMissingValue(Integer.MAX_VALUE);
+    Sort sort = new Sort(sortField);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as a Integer.MAX_VALUE
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type int in reverse */
+  public void testIntReverse() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new IntPoint("value", 300000));
+    doc.add(new StoredField("value", 300000));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new IntPoint("value", -1));
+    doc.add(new StoredField("value", -1));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new IntPoint("value", 4));
+    doc.add(new StoredField("value", 4));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.INTEGER_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.INT, true));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // reverse numeric order
+    assertEquals("300000", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("-1", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+
+  /** Tests sorting on type legacy int */
+  public void testLegacyInt() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new LegacyIntField("value", 300000, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyIntField("value", -1, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyIntField("value", 4, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_INTEGER));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.INT));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // numeric order
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("300000", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy int with a missing value */
+  public void testLegacyIntMissing() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyIntField("value", -1, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyIntField("value", 4, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_INTEGER));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.INT));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as a 0
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy int, specifying the missing value should be treated as Integer.MAX_VALUE */
+  public void testLegacyIntMissingLast() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyIntField("value", -1, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyIntField("value", 4, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_INTEGER));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    SortField sortField = new SortField("value", SortField.Type.INT);
+    sortField.setMissingValue(Integer.MAX_VALUE);
+    Sort sort = new Sort(sortField);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as a Integer.MAX_VALUE
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy int in reverse */
+  public void testLegacyIntReverse() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new LegacyIntField("value", 300000, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyIntField("value", -1, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyIntField("value", 4, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_INTEGER));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.INT, true));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // reverse numeric order
+    assertEquals("300000", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("-1", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type long */
+  public void testLong() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new LongPoint("value", 3000000000L));
+    doc.add(new StoredField("value", 3000000000L));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LongPoint("value", -1));
+    doc.add(new StoredField("value", -1));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LongPoint("value", 4));
+    doc.add(new StoredField("value", 4));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LONG_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.LONG));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // numeric order
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("3000000000", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type long with a missing value */
+  public void testLongMissing() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LongPoint("value", -1));
+    doc.add(new StoredField("value", -1));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LongPoint("value", 4));
+    doc.add(new StoredField("value", 4));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LONG_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.LONG));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as 0
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type long, specifying the missing value should be treated as Long.MAX_VALUE */
+  public void testLongMissingLast() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LongPoint("value", -1));
+    doc.add(new StoredField("value", -1));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LongPoint("value", 4));
+    doc.add(new StoredField("value", 4));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LONG_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    SortField sortField = new SortField("value", SortField.Type.LONG);
+    sortField.setMissingValue(Long.MAX_VALUE);
+    Sort sort = new Sort(sortField);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as Long.MAX_VALUE
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type long in reverse */
+  public void testLongReverse() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new LongPoint("value", 3000000000L));
+    doc.add(new StoredField("value", 3000000000L));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LongPoint("value", -1));
+    doc.add(new StoredField("value", -1));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LongPoint("value", 4));
+    doc.add(new StoredField("value", 4));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LONG_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.LONG, true));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // reverse numeric order
+    assertEquals("3000000000", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("-1", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy long */
+  public void testLegacyLong() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new LegacyLongField("value", 3000000000L, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyLongField("value", -1, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyLongField("value", 4, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_LONG));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.LONG));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // numeric order
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("3000000000", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy long with a missing value */
+  public void testLegacyLongMissing() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyLongField("value", -1, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyLongField("value", 4, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_LONG));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.LONG));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as 0
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy long, specifying the missing value should be treated as Long.MAX_VALUE */
+  public void testLegacyLongMissingLast() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyLongField("value", -1, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyLongField("value", 4, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_LONG));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    SortField sortField = new SortField("value", SortField.Type.LONG);
+    sortField.setMissingValue(Long.MAX_VALUE);
+    Sort sort = new Sort(sortField);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as Long.MAX_VALUE
+    assertEquals("-1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy long in reverse */
+  public void testLegacyLongReverse() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new LegacyLongField("value", 3000000000L, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyLongField("value", -1, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyLongField("value", 4, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_LONG));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.LONG, true));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // reverse numeric order
+    assertEquals("3000000000", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("-1", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type float */
+  public void testFloat() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new FloatPoint("value", 30.1f));
+    doc.add(new StoredField("value", 30.1f));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new FloatPoint("value", -1.3f));
+    doc.add(new StoredField("value", -1.3f));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new FloatPoint("value", 4.2f));
+    doc.add(new StoredField("value", 4.2f));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.FLOAT_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.FLOAT));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // numeric order
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("30.1", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type float with a missing value */
+  public void testFloatMissing() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new FloatPoint("value", -1.3f));
+    doc.add(new StoredField("value", -1.3f));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new FloatPoint("value", 4.2f));
+    doc.add(new StoredField("value", 4.2f));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.FLOAT_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.FLOAT));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as 0
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4.2", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type float, specifying the missing value should be treated as Float.MAX_VALUE */
+  public void testFloatMissingLast() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new FloatPoint("value", -1.3f));
+    doc.add(new StoredField("value", -1.3f));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new FloatPoint("value", 4.2f));
+    doc.add(new StoredField("value", 4.2f));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.FLOAT_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    SortField sortField = new SortField("value", SortField.Type.FLOAT);
+    sortField.setMissingValue(Float.MAX_VALUE);
+    Sort sort = new Sort(sortField);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as Float.MAX_VALUE
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type float in reverse */
+  public void testFloatReverse() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new FloatPoint("value", 30.1f));
+    doc.add(new StoredField("value", 30.1f));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new FloatPoint("value", -1.3f));
+    doc.add(new StoredField("value", -1.3f));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new FloatPoint("value", 4.2f));
+    doc.add(new StoredField("value", 4.2f));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.FLOAT_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.FLOAT, true));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // reverse numeric order
+    assertEquals("30.1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy float */
+  public void testLegacyFloat() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new LegacyFloatField("value", 30.1f, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyFloatField("value", -1.3f, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyFloatField("value", 4.2f, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_FLOAT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.FLOAT));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // numeric order
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("30.1", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy float with a missing value */
+  public void testLegacyFloatMissing() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyFloatField("value", -1.3f, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyFloatField("value", 4.2f, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_FLOAT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.FLOAT));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as 0
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4.2", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy float, specifying the missing value should be treated as Float.MAX_VALUE */
+  public void testLegacyFloatMissingLast() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyFloatField("value", -1.3f, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyFloatField("value", 4.2f, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_FLOAT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    SortField sortField = new SortField("value", SortField.Type.FLOAT);
+    sortField.setMissingValue(Float.MAX_VALUE);
+    Sort sort = new Sort(sortField);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // null is treated as Float.MAX_VALUE
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy float in reverse */
+  public void testLegacyFloatReverse() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new LegacyFloatField("value", 30.1f, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyFloatField("value", -1.3f, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyFloatField("value", 4.2f, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_FLOAT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.FLOAT, true));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(3, td.totalHits);
+    // reverse numeric order
+    assertEquals("30.1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type double */
+  public void testDouble() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new DoublePoint("value", 30.1));
+    doc.add(new StoredField("value", 30.1));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", -1.3));
+    doc.add(new StoredField("value", -1.3));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", 4.2333333333333));
+    doc.add(new StoredField("value", 4.2333333333333));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", 4.2333333333332));
+    doc.add(new StoredField("value", 4.2333333333332));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.DOUBLE_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.DOUBLE));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(4, td.totalHits);
+    // numeric order
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2333333333332", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4.2333333333333", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    assertEquals("30.1", searcher.doc(td.scoreDocs[3].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type double with +/- zero */
+  public void testDoubleSignedZero() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new DoublePoint("value", +0d));
+    doc.add(new StoredField("value", +0d));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", -0d));
+    doc.add(new StoredField("value", -0d));
+    writer.addDocument(doc);
+    doc = new Document();
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.DOUBLE_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.DOUBLE));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(2, td.totalHits);
+    // numeric order
+    double v0 = searcher.doc(td.scoreDocs[0].doc).getField("value").numericValue().doubleValue();
+    double v1 = searcher.doc(td.scoreDocs[1].doc).getField("value").numericValue().doubleValue();
+    assertEquals(0, v0, 0d);
+    assertEquals(0, v1, 0d);
+    // check sign bits
+    assertEquals(1, Double.doubleToLongBits(v0) >>> 63);
+    assertEquals(0, Double.doubleToLongBits(v1) >>> 63);
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type double with a missing value */
+  public void testDoubleMissing() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", -1.3));
+    doc.add(new StoredField("value", -1.3));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", 4.2333333333333));
+    doc.add(new StoredField("value", 4.2333333333333));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", 4.2333333333332));
+    doc.add(new StoredField("value", 4.2333333333332));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.DOUBLE_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.DOUBLE));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(4, td.totalHits);
+    // null treated as a 0
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4.2333333333332", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    assertEquals("4.2333333333333", searcher.doc(td.scoreDocs[3].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type double, specifying the missing value should be treated as Double.MAX_VALUE */
+  public void testDoubleMissingLast() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", -1.3));
+    doc.add(new StoredField("value", -1.3));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", 4.2333333333333));
+    doc.add(new StoredField("value", 4.2333333333333));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", 4.2333333333332));
+    doc.add(new StoredField("value", 4.2333333333332));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.DOUBLE_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    SortField sortField = new SortField("value", SortField.Type.DOUBLE);
+    sortField.setMissingValue(Double.MAX_VALUE);
+    Sort sort = new Sort(sortField);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(4, td.totalHits);
+    // null treated as Double.MAX_VALUE
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2333333333332", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4.2333333333333", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[3].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type double in reverse */
+  public void testDoubleReverse() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new DoublePoint("value", 30.1));
+    doc.add(new StoredField("value", 30.1));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", -1.3));
+    doc.add(new StoredField("value", -1.3));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", 4.2333333333333));
+    doc.add(new StoredField("value", 4.2333333333333));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new DoublePoint("value", 4.2333333333332));
+    doc.add(new StoredField("value", 4.2333333333332));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.DOUBLE_POINT));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir, false);
+    Sort sort = new Sort(new SortField("value", SortField.Type.DOUBLE, true));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(4, td.totalHits);
+    // numeric order
+    assertEquals("30.1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2333333333333", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4.2333333333332", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[3].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+
+  /** Tests sorting on type legacy double */
+  public void testLegacyDouble() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new LegacyDoubleField("value", 30.1, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", -1.3, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", 4.2333333333333, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", 4.2333333333332, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_DOUBLE));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.DOUBLE));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(4, td.totalHits);
+    // numeric order
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2333333333332", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4.2333333333333", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    assertEquals("30.1", searcher.doc(td.scoreDocs[3].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy double with +/- zero */
+  public void testLegacyDoubleSignedZero() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new LegacyDoubleField("value", +0d, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", -0d, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_DOUBLE));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.DOUBLE));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(2, td.totalHits);
+    // numeric order
+    double v0 = searcher.doc(td.scoreDocs[0].doc).getField("value").numericValue().doubleValue();
+    double v1 = searcher.doc(td.scoreDocs[1].doc).getField("value").numericValue().doubleValue();
+    assertEquals(0, v0, 0d);
+    assertEquals(0, v1, 0d);
+    // check sign bits
+    assertEquals(1, Double.doubleToLongBits(v0) >>> 63);
+    assertEquals(0, Double.doubleToLongBits(v1) >>> 63);
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy double with a missing value */
+  public void testLegacyDoubleMissing() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", -1.3, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", 4.2333333333333, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", 4.2333333333332, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_DOUBLE));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.DOUBLE));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(4, td.totalHits);
+    // null treated as a 0
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4.2333333333332", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    assertEquals("4.2333333333333", searcher.doc(td.scoreDocs[3].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy double, specifying the missing value should be treated as Double.MAX_VALUE */
+  public void testLegacyDoubleMissingLast() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", -1.3, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", 4.2333333333333, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", 4.2333333333332, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_DOUBLE));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    SortField sortField = new SortField("value", SortField.Type.DOUBLE);
+    sortField.setMissingValue(Double.MAX_VALUE);
+    Sort sort = new Sort(sortField);
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(4, td.totalHits);
+    // null treated as Double.MAX_VALUE
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2333333333332", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4.2333333333333", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    assertNull(searcher.doc(td.scoreDocs[3].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting on type legacy double in reverse */
+  public void testLegacyDoubleReverse() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(new LegacyDoubleField("value", 30.1, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", -1.3, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", 4.2333333333333, Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(new LegacyDoubleField("value", 4.2333333333332, Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), 
+                     Collections.singletonMap("value", Type.LEGACY_DOUBLE));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.DOUBLE, true));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(4, td.totalHits);
+    // numeric order
+    assertEquals("30.1", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("4.2333333333333", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    assertEquals("4.2333333333332", searcher.doc(td.scoreDocs[2].doc).get("value"));
+    assertEquals("-1.3", searcher.doc(td.scoreDocs[3].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  public void testEmptyStringVsNullStringSort() throws Exception {
+    Directory dir = newDirectory();
+    IndexWriter w = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random())));
+    Document doc = new Document();
+    doc.add(newStringField("f", "", Field.Store.NO));
+    doc.add(newStringField("t", "1", Field.Store.NO));
+    w.addDocument(doc);
+    w.commit();
+    doc = new Document();
+    doc.add(newStringField("t", "1", Field.Store.NO));
+    w.addDocument(doc);
+
+    IndexReader r = UninvertingReader.wrap(DirectoryReader.open(w), 
+                    Collections.singletonMap("f", Type.SORTED));
+    w.close();
+    IndexSearcher s = newSearcher(r);
+    TopDocs hits = s.search(new TermQuery(new Term("t", "1")), 10, new Sort(new SortField("f", SortField.Type.STRING)));
+    assertEquals(2, hits.totalHits);
+    // null sorts first
+    assertEquals(1, hits.scoreDocs[0].doc);
+    assertEquals(0, hits.scoreDocs[1].doc);
+    TestUtil.checkReader(r);
+    r.close();
+    dir.close();
+  }
+  
+  /** test that we throw exception on multi-valued field, creates corrupt reader, use SORTED_SET instead */
+  public void testMultiValuedField() throws IOException {
+    Directory indexStore = newDirectory();
+    IndexWriter writer = new IndexWriter(indexStore, newIndexWriterConfig(new MockAnalyzer(random())));
+    for(int i=0; i<5; i++) {
+        Document doc = new Document();
+        doc.add(new StringField("string", "a"+i, Field.Store.NO));
+        doc.add(new StringField("string", "b"+i, Field.Store.NO));
+        writer.addDocument(doc);
+    }
+    writer.forceMerge(1); // enforce one segment to have a higher unique term count in all cases
+    writer.close();
+    Sort sort = new Sort(
+        new SortField("string", SortField.Type.STRING),
+        SortField.FIELD_DOC);
+    IndexReader reader = UninvertingReader.wrap(DirectoryReader.open(indexStore),
+                         Collections.singletonMap("string", Type.SORTED));
+    IndexSearcher searcher = new IndexSearcher(reader);
+    expectThrows(IllegalStateException.class, () -> {
+      searcher.search(new MatchAllDocsQuery(), 500, sort);
+    });
+    reader.close();
+    indexStore.close();
+  }
+  
+  public void testMaxScore() throws Exception {
+    Directory d = newDirectory();
+    // Not RIW because we need exactly 2 segs:
+    IndexWriter w = new IndexWriter(d, new IndexWriterConfig(new MockAnalyzer(random())));
+    int id = 0;
+    for(int seg=0;seg<2;seg++) {
+      for(int docIDX=0;docIDX<10;docIDX++) {
+        Document doc = new Document();
+        doc.add(new LegacyIntField("id", docIDX, Field.Store.YES));
+        StringBuilder sb = new StringBuilder();
+        for(int i=0;i<id;i++) {
+          sb.append(' ');
+          sb.append("text");
+        }
+        doc.add(newTextField("body", sb.toString(), Field.Store.NO));
+        w.addDocument(doc);
+        id++;
+      }
+      w.commit();
+    }
+
+    IndexReader r = UninvertingReader.wrap(DirectoryReader.open(w),
+                    Collections.singletonMap("id", Type.LEGACY_INTEGER));
+    w.close();
+    Query q = new TermQuery(new Term("body", "text"));
+    IndexSearcher s = newSearcher(r);
+    float maxScore = s.search(q , 10).getMaxScore();
+    assertEquals(maxScore, s.search(q, 3, Sort.INDEXORDER, random().nextBoolean(), true).getMaxScore(), 0.0);
+    assertEquals(maxScore, s.search(q, 3, Sort.RELEVANCE, random().nextBoolean(), true).getMaxScore(), 0.0);
+    assertEquals(maxScore, s.search(q, 3, new Sort(new SortField[] {new SortField("id", SortField.Type.INT, false)}), random().nextBoolean(), true).getMaxScore(), 0.0);
+    assertEquals(maxScore, s.search(q, 3, new Sort(new SortField[] {new SortField("id", SortField.Type.INT, true)}), random().nextBoolean(), true).getMaxScore(), 0.0);
+    TestUtil.checkReader(r);
+    r.close();
+    d.close();
+  }
+  
+  /** test sorts when there's nothing in the index */
+  public void testEmptyIndex() throws Exception {
+    IndexSearcher empty = newSearcher(new MultiReader());
+    Query query = new TermQuery(new Term("contents", "foo"));
+  
+    Sort sort = new Sort();
+    TopDocs td = empty.search(query, 10, sort, true, true);
+    assertEquals(0, td.totalHits);
+
+    sort.setSort(SortField.FIELD_DOC);
+    td = empty.search(query, 10, sort, true, true);
+    assertEquals(0, td.totalHits);
+
+    sort.setSort(new SortField("int", SortField.Type.INT), SortField.FIELD_DOC);
+    td = empty.search(query, 10, sort, true, true);
+    assertEquals(0, td.totalHits);
+    
+    sort.setSort(new SortField("string", SortField.Type.STRING, true), SortField.FIELD_DOC);
+    td = empty.search(query, 10, sort, true, true);
+    assertEquals(0, td.totalHits);
+    
+    sort.setSort(new SortField("string_val", SortField.Type.STRING_VAL, true), SortField.FIELD_DOC);
+    td = empty.search(query, 10, sort, true, true);
+    assertEquals(0, td.totalHits);
+
+    sort.setSort(new SortField("float", SortField.Type.FLOAT), new SortField("string", SortField.Type.STRING));
+    td = empty.search(query, 10, sort, true, true);
+    assertEquals(0, td.totalHits);
+  }
+  
+  /** Tests sorting a single document */
+  public void testSortOneDocument() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(),
+                     Collections.singletonMap("value", Type.SORTED));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.STRING));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(1, td.totalHits);
+    assertEquals("foo", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting a single document with scores */
+  public void testSortOneDocumentWithScores() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.YES));
+    writer.addDocument(doc);
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(),
+                     Collections.singletonMap("value", Type.SORTED));
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(new SortField("value", SortField.Type.STRING));
+
+    TopDocs expected = searcher.search(new TermQuery(new Term("value", "foo")), 10);
+    assertEquals(1, expected.totalHits);
+    TopDocs actual = searcher.search(new TermQuery(new Term("value", "foo")), 10, sort, true, true);
+    
+    assertEquals(expected.totalHits, actual.totalHits);
+    assertEquals(expected.scoreDocs[0].score, actual.scoreDocs[0].score, 0F);
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+  
+  /** Tests sorting with two fields */
+  public void testSortTwoFields() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newStringField("tievalue", "tied", Field.Store.NO));
+    doc.add(newStringField("value", "foo", Field.Store.YES));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("tievalue", "tied", Field.Store.NO));
+    doc.add(newStringField("value", "bar", Field.Store.YES));
+    writer.addDocument(doc);
+    Map<String,Type> mappings = new HashMap<>();
+    mappings.put("tievalue", Type.SORTED);
+    mappings.put("value", Type.SORTED);
+    
+    IndexReader ir = UninvertingReader.wrap(writer.getReader(), mappings);
+    writer.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    // tievalue, then value
+    Sort sort = new Sort(new SortField("tievalue", SortField.Type.STRING),
+                         new SortField("value", SortField.Type.STRING));
+
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
+    assertEquals(2, td.totalHits);
+    // 'bar' comes before 'foo'
+    assertEquals("bar", searcher.doc(td.scoreDocs[0].doc).get("value"));
+    assertEquals("foo", searcher.doc(td.scoreDocs[1].doc).get("value"));
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+
+  public void testScore() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newStringField("value", "bar", Field.Store.NO));
+    writer.addDocument(doc);
+    doc = new Document();
+    doc.add(newStringField("value", "foo", Field.Store.NO));
+    writer.addDocument(doc);
+    IndexReader ir = writer.getReader();
+    writer.close();
+
+    IndexSearcher searcher = newSearcher(ir);
+    Sort sort = new Sort(SortField.FIELD_SCORE);
+
+    final BooleanQuery.Builder bq = new BooleanQuery.Builder();
+    bq.add(new TermQuery(new Term("value", "foo")), Occur.SHOULD);
+    bq.add(new MatchAllDocsQuery(), Occur.SHOULD);
+    TopDocs td = searcher.search(bq.build(), 10, sort);
+    assertEquals(2, td.totalHits);
+    if (Float.isNaN(td.scoreDocs[0].score) == false && Float.isNaN(td.scoreDocs[1].score) == false) {
+      assertEquals(1, td.scoreDocs[0].doc);
+      assertEquals(0, td.scoreDocs[1].doc);
+    }
+    TestUtil.checkReader(ir);
+    ir.close();
+    dir.close();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/baa5344e/solr/core/src/test/org/apache/solr/uninverting/TestFieldCacheSortRandom.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/uninverting/TestFieldCacheSortRandom.java b/solr/core/src/test/org/apache/solr/uninverting/TestFieldCacheSortRandom.java
new file mode 100644
index 0000000..6f2e17c
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/uninverting/TestFieldCacheSortRandom.java
@@ -0,0 +1,318 @@
+/*
+ * 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.uninverting;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.Set;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.IntPoint;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.ConstantScoreScorer;
+import org.apache.lucene.search.ConstantScoreWeight;
+import org.apache.lucene.search.FieldDoc;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.TopFieldDocs;
+import org.apache.lucene.search.Weight;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.BitSetIterator;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.FixedBitSet;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+import org.apache.solr.uninverting.UninvertingReader.Type;
+
+/** random sorting tests with uninversion */
+public class TestFieldCacheSortRandom extends LuceneTestCase {
+
+  public void testRandomStringSort() throws Exception {
+    testRandomStringSort(SortField.Type.STRING);
+  }
+
+  public void testRandomStringValSort() throws Exception {
+    testRandomStringSort(SortField.Type.STRING_VAL);
+  }
+
+  private void testRandomStringSort(SortField.Type type) throws Exception {
+    Random random = new Random(random().nextLong());
+
+    final int NUM_DOCS = atLeast(100);
+    final Directory dir = newDirectory();
+    final RandomIndexWriter writer = new RandomIndexWriter(random, dir);
+    final boolean allowDups = random.nextBoolean();
+    final Set<String> seen = new HashSet<>();
+    final int maxLength = TestUtil.nextInt(random, 5, 100);
+    if (VERBOSE) {
+      System.out.println("TEST: NUM_DOCS=" + NUM_DOCS + " maxLength=" + maxLength + " allowDups=" + allowDups);
+    }
+
+    int numDocs = 0;
+    final List<BytesRef> docValues = new ArrayList<>();
+    // TODO: deletions
+    while (numDocs < NUM_DOCS) {
+      final Document doc = new Document();
+
+      // 10% of the time, the document is missing the value:
+      final BytesRef br;
+      if (random().nextInt(10) != 7) {
+        final String s;
+        if (random.nextBoolean()) {
+          s = TestUtil.randomSimpleString(random, maxLength);
+        } else {
+          s = TestUtil.randomUnicodeString(random, maxLength);
+        }
+
+        if (!allowDups) {
+          if (seen.contains(s)) {
+            continue;
+          }
+          seen.add(s);
+        }
+
+        if (VERBOSE) {
+          System.out.println("  " + numDocs + ": s=" + s);
+        }
+
+        doc.add(new StringField("stringdv", s, Field.Store.NO));
+        docValues.add(new BytesRef(s));
+
+      } else {
+        br = null;
+        if (VERBOSE) {
+          System.out.println("  " + numDocs + ": <missing>");
+        }
+        docValues.add(null);
+      }
+
+      doc.add(new IntPoint("id", numDocs));
+      doc.add(new StoredField("id", numDocs));
+      writer.addDocument(doc);
+      numDocs++;
+
+      if (random.nextInt(40) == 17) {
+        // force flush
+        writer.getReader().close();
+      }
+    }
+
+    Map<String,UninvertingReader.Type> mapping = new HashMap<>();
+    mapping.put("stringdv", Type.SORTED);
+    mapping.put("id", Type.INTEGER_POINT);
+    final IndexReader r = UninvertingReader.wrap(writer.getReader(), mapping);
+    writer.close();
+    if (VERBOSE) {
+      System.out.println("  reader=" + r);
+    }
+    
+    final IndexSearcher s = newSearcher(r, false);
+    final int ITERS = atLeast(100);
+    for(int iter=0;iter<ITERS;iter++) {
+      final boolean reverse = random.nextBoolean();
+
+      final TopFieldDocs hits;
+      final SortField sf;
+      final boolean sortMissingLast;
+      final boolean missingIsNull;
+      sf = new SortField("stringdv", type, reverse);
+      sortMissingLast = random().nextBoolean();
+      missingIsNull = true;
+
+      if (sortMissingLast) {
+        sf.setMissingValue(SortField.STRING_LAST);
+      }
+      
+      final Sort sort;
+      if (random.nextBoolean()) {
+        sort = new Sort(sf);
+      } else {
+        sort = new Sort(sf, SortField.FIELD_DOC);
+      }
+      final int hitCount = TestUtil.nextInt(random, 1, r.maxDoc() + 20);
+      final RandomQuery f = new RandomQuery(random.nextLong(), random.nextFloat(), docValues);
+      int queryType = random.nextInt(2);
+      if (queryType == 0) {
+        hits = s.search(new ConstantScoreQuery(f),
+                        hitCount, sort, random.nextBoolean(), random.nextBoolean());
+      } else {
+        hits = s.search(f, hitCount, sort, random.nextBoolean(), random.nextBoolean());
+      }
+
+      if (VERBOSE) {
+        System.out.println("\nTEST: iter=" + iter + " " + hits.totalHits + " hits; topN=" + hitCount + "; reverse=" + reverse + "; sortMissingLast=" + sortMissingLast + " sort=" + sort);
+      }
+
+      // Compute expected results:
+      Collections.sort(f.matchValues, new Comparator<BytesRef>() {
+          @Override
+          public int compare(BytesRef a, BytesRef b) {
+            if (a == null) {
+              if (b == null) {
+                return 0;
+              }
+              if (sortMissingLast) {
+                return 1;
+              } else {
+                return -1;
+              }
+            } else if (b == null) {
+              if (sortMissingLast) {
+                return -1;
+              } else {
+                return 1;
+              }
+            } else {
+              return a.compareTo(b);
+            }
+          }
+        });
+
+      if (reverse) {
+        Collections.reverse(f.matchValues);
+      }
+      final List<BytesRef> expected = f.matchValues;
+      if (VERBOSE) {
+        System.out.println("  expected:");
+        for(int idx=0;idx<expected.size();idx++) {
+          BytesRef br = expected.get(idx);
+          if (br == null && missingIsNull == false) {
+            br = new BytesRef();
+          }
+          System.out.println("    " + idx + ": " + (br == null ? "<missing>" : br.utf8ToString()));
+          if (idx == hitCount-1) {
+            break;
+          }
+        }
+      }
+      
+      if (VERBOSE) {
+        System.out.println("  actual:");
+        for(int hitIDX=0;hitIDX<hits.scoreDocs.length;hitIDX++) {
+          final FieldDoc fd = (FieldDoc) hits.scoreDocs[hitIDX];
+          BytesRef br = (BytesRef) fd.fields[0];
+
+          System.out.println("    " + hitIDX + ": " + (br == null ? "<missing>" : br.utf8ToString()) + " id=" + s.doc(fd.doc).get("id"));
+        }
+      }
+      for(int hitIDX=0;hitIDX<hits.scoreDocs.length;hitIDX++) {
+        final FieldDoc fd = (FieldDoc) hits.scoreDocs[hitIDX];
+        BytesRef br = expected.get(hitIDX);
+        if (br == null && missingIsNull == false) {
+          br = new BytesRef();
+        }
+
+        // Normally, the old codecs (that don't support
+        // docsWithField via doc values) will always return
+        // an empty BytesRef for the missing case; however,
+        // if all docs in a given segment were missing, in
+        // that case it will return null!  So we must map
+        // null here, too:
+        BytesRef br2 = (BytesRef) fd.fields[0];
+        if (br2 == null && missingIsNull == false) {
+          br2 = new BytesRef();
+        }
+        
+        assertEquals(br, br2);
+      }
+    }
+
+    r.close();
+    dir.close();
+  }
+  
+  private static class RandomQuery extends Query {
+    private final long seed;
+    private float density;
+    private final List<BytesRef> docValues;
+    public final List<BytesRef> matchValues = Collections.synchronizedList(new ArrayList<BytesRef>());
+
+    // density should be 0.0 ... 1.0
+    public RandomQuery(long seed, float density, List<BytesRef> docValues) {
+      this.seed = seed;
+      this.density = density;
+      this.docValues = docValues;
+    }
+
+    @Override
+    public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
+      return new ConstantScoreWeight(this) {
+        @Override
+        public Scorer scorer(LeafReaderContext context) throws IOException {
+          Random random = new Random(seed ^ context.docBase);
+          final int maxDoc = context.reader().maxDoc();
+          final NumericDocValues idSource = DocValues.getNumeric(context.reader(), "id");
+          assertNotNull(idSource);
+          final FixedBitSet bits = new FixedBitSet(maxDoc);
+          for(int docID=0;docID<maxDoc;docID++) {
+            if (random.nextFloat() <= density) {
+              bits.set(docID);
+              //System.out.println("  acc id=" + idSource.getInt(docID) + " docID=" + docID);
+              matchValues.add(docValues.get((int) idSource.get(docID)));
+            }
+          }
+
+          return new ConstantScoreScorer(this, score(), new BitSetIterator(bits, bits.approximateCardinality()));
+        }
+      };
+    }
+
+    @Override
+    public String toString(String field) {
+      return "RandomFilter(density=" + density + ")";
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      return sameClassAs(other) &&
+             equalsTo(getClass().cast(other));
+    }
+
+    private boolean equalsTo(RandomQuery other) {
+      return seed == other.seed && 
+             docValues == other.docValues &&
+             density == other.density;
+    }
+
+    @Override
+    public int hashCode() {
+      int h = classHash();
+      h = 31 * h + Objects.hash(seed, density);
+      h = 31 * h + System.identityHashCode(docValues);
+      return h;
+    }
+  }
+}


Mime
View raw message