org.apache.solr.uninverting.TestFieldCacheSort.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.uninverting.TestFieldCacheSort.java

Source

/*
 * 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.Field;
import org.apache.lucene.document.FloatPoint;
import org.apache.lucene.document.IntPoint;
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.legacy.LegacyDoubleField;
import org.apache.lucene.legacy.LegacyFloatField;
import org.apache.lucene.legacy.LegacyIntField;
import org.apache.lucene.legacy.LegacyLongField;
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();
    }
}