lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sha...@apache.org
Subject [06/50] [abbrv] lucene-solr:jira/solr-11990: SOLR-11598: Support more than 4 sort fields in the export writer
Date Sat, 28 Jul 2018 04:49:29 GMT
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/FloatCmp.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/FloatCmp.java b/solr/core/src/java/org/apache/solr/handler/export/FloatCmp.java
new file mode 100644
index 0000000..7ef078c
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/FloatCmp.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.export;
+
+interface FloatComp {
+  int compare(float a, float b);
+  float resetValue();
+}
+
+class FloatAsc implements FloatComp {
+  public float resetValue() {
+    return Float.MAX_VALUE;
+  }
+
+  public int compare(float a, float b) {
+    return Float.compare(b, a);
+  }
+}
+
+class FloatDesc implements FloatComp {
+  public float resetValue() {
+    return -Float.MAX_VALUE;
+  }
+
+  public int compare(float a, float b) {
+    return Float.compare(a, b);
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/FloatFieldWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/FloatFieldWriter.java b/solr/core/src/java/org/apache/solr/handler/export/FloatFieldWriter.java
new file mode 100644
index 0000000..cfb2e25
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/FloatFieldWriter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.handler.export;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.solr.common.MapWriter;
+
+class FloatFieldWriter extends FieldWriter {
+  private String field;
+
+  public FloatFieldWriter(String field) {
+    this.field = field;
+  }
+
+  public boolean write(int docId, LeafReader reader, MapWriter.EntryWriter ew, int fieldIndex) throws IOException {
+    NumericDocValues vals = DocValues.getNumeric(reader, this.field);
+    int val;
+    if (vals.advance(docId) == docId) {
+      val = (int)vals.longValue();
+    } else {
+      return false;
+    }
+    ew.put(this.field, Float.intBitsToFloat(val));
+    return true;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/FloatValue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/FloatValue.java b/solr/core/src/java/org/apache/solr/handler/export/FloatValue.java
new file mode 100644
index 0000000..f6a5bbd
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/FloatValue.java
@@ -0,0 +1,78 @@
+/*
+ * 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.handler.export;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.NumericDocValues;
+
+class FloatValue implements SortValue {
+
+  protected NumericDocValues vals;
+  protected String field;
+  protected float currentValue;
+  protected FloatComp comp;
+  private int lastDocID;
+
+  public FloatValue(String field, FloatComp comp) {
+    this.field = field;
+    this.comp = comp;
+    this.currentValue = comp.resetValue();
+  }
+
+  public FloatValue copy() {
+    return new FloatValue(field, comp);
+  }
+
+  public void setNextReader(LeafReaderContext context) throws IOException {
+    this.vals = DocValues.getNumeric(context.reader(), field);
+    lastDocID = 0;
+  }
+
+  public void setCurrentValue(int docId) throws IOException {
+    if (docId < lastDocID) {
+      throw new AssertionError("docs were sent out-of-order: lastDocID=" + lastDocID + " vs doc=" + docId);
+    }
+    lastDocID = docId;
+    int curDocID = vals.docID();
+    if (docId > curDocID) {
+      curDocID = vals.advance(docId);
+    }
+    if (docId == curDocID) {
+      currentValue = Float.intBitsToFloat((int)vals.longValue());
+    } else {
+      currentValue = 0f;
+    }
+  }
+
+  public void setCurrentValue(SortValue sv) {
+    FloatValue fv = (FloatValue)sv;
+    this.currentValue = fv.currentValue;
+  }
+
+  public void reset() {
+    this.currentValue = comp.resetValue();
+  }
+
+  public int compareTo(SortValue o) {
+    FloatValue fv = (FloatValue)o;
+    return comp.compare(currentValue, fv.currentValue);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/IntComp.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/IntComp.java b/solr/core/src/java/org/apache/solr/handler/export/IntComp.java
new file mode 100644
index 0000000..ac83d5d
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/IntComp.java
@@ -0,0 +1,45 @@
+/*
+ * 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.handler.export;
+
+public interface IntComp {
+  int compare(int a, int b);
+  int resetValue();
+}
+
+class IntAsc implements IntComp {
+
+  public int resetValue() {
+    return Integer.MAX_VALUE;
+  }
+
+  public int compare(int a, int b) {
+    return Integer.compare(b, a);
+  }
+}
+
+class IntDesc implements IntComp {
+
+  public int resetValue() {
+    return Integer.MIN_VALUE;
+  }
+
+  public int compare(int a, int b) {
+    return Integer.compare(a, b);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/IntFieldWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/IntFieldWriter.java b/solr/core/src/java/org/apache/solr/handler/export/IntFieldWriter.java
new file mode 100644
index 0000000..66e5936
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/IntFieldWriter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.handler.export;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.solr.common.MapWriter;
+
+class IntFieldWriter extends FieldWriter {
+  private String field;
+
+  public IntFieldWriter(String field) {
+    this.field = field;
+  }
+
+  public boolean write(int docId, LeafReader reader, MapWriter.EntryWriter ew, int fieldIndex) throws IOException {
+    NumericDocValues vals = DocValues.getNumeric(reader, this.field);
+    int val;
+    if (vals.advance(docId) == docId) {
+      val = (int) vals.longValue();
+    } else {
+      return false;
+    }
+    ew.put(this.field, val);
+    return true;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/IntValue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/IntValue.java b/solr/core/src/java/org/apache/solr/handler/export/IntValue.java
new file mode 100644
index 0000000..d222aa3
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/IntValue.java
@@ -0,0 +1,77 @@
+/*
+ * 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.handler.export;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.NumericDocValues;
+
+public class IntValue implements SortValue {
+
+  protected NumericDocValues vals;
+  protected String field;
+  protected int currentValue;
+  protected IntComp comp;
+  private int lastDocID;
+
+  public IntValue copy() {
+    return new IntValue(field, comp);
+  }
+
+  public IntValue(String field, IntComp comp) {
+    this.field = field;
+    this.comp = comp;
+    this.currentValue = comp.resetValue();
+  }
+
+  public void setNextReader(LeafReaderContext context) throws IOException {
+    this.vals = DocValues.getNumeric(context.reader(), field);
+    lastDocID = 0;
+  }
+
+  public void setCurrentValue(int docId) throws IOException {
+    if (docId < lastDocID) {
+      throw new AssertionError("docs were sent out-of-order: lastDocID=" + lastDocID + " vs doc=" + docId);
+    }
+    lastDocID = docId;
+    int curDocID = vals.docID();
+    if (docId > curDocID) {
+      curDocID = vals.advance(docId);
+    }
+    if (docId == curDocID) {
+      currentValue = (int) vals.longValue();
+    } else {
+      currentValue = 0;
+    }
+  }
+
+  public int compareTo(SortValue o) {
+    IntValue iv = (IntValue)o;
+    return comp.compare(currentValue, iv.currentValue);
+  }
+
+  public void setCurrentValue (SortValue value) {
+    currentValue = ((IntValue)value).currentValue;
+  }
+
+  public void reset() {
+    currentValue = comp.resetValue();
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/LongCmp.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/LongCmp.java b/solr/core/src/java/org/apache/solr/handler/export/LongCmp.java
new file mode 100644
index 0000000..7d997ac
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/LongCmp.java
@@ -0,0 +1,45 @@
+/*
+ * 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.handler.export;
+
+interface LongComp {
+  int compare(long a, long b);
+  long resetValue();
+}
+
+class LongAsc implements LongComp {
+
+  public long resetValue() {
+    return Long.MAX_VALUE;
+  }
+
+  public int compare(long a, long b) {
+    return Long.compare(b, a);
+  }
+}
+
+class LongDesc implements LongComp {
+
+  public long resetValue() {
+    return Long.MIN_VALUE;
+  }
+
+  public int compare(long a, long b) {
+    return Long.compare(a, b);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/LongFieldWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/LongFieldWriter.java b/solr/core/src/java/org/apache/solr/handler/export/LongFieldWriter.java
new file mode 100644
index 0000000..87f2a32
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/LongFieldWriter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.handler.export;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.solr.common.MapWriter;
+
+class LongFieldWriter extends FieldWriter {
+  private String field;
+
+  public LongFieldWriter(String field) {
+    this.field = field;
+  }
+
+  public boolean write(int docId, LeafReader reader, MapWriter.EntryWriter ew, int fieldIndex) throws IOException {
+    NumericDocValues vals = DocValues.getNumeric(reader, this.field);
+    long val;
+    if (vals.advance(docId) == docId) {
+      val = vals.longValue();
+    } else {
+      return false;
+    }
+    ew.put(field, val);
+    return true;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/LongValue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/LongValue.java b/solr/core/src/java/org/apache/solr/handler/export/LongValue.java
new file mode 100644
index 0000000..28b9a07
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/LongValue.java
@@ -0,0 +1,78 @@
+/*
+ * 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.handler.export;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.NumericDocValues;
+
+public class LongValue implements SortValue {
+
+  protected NumericDocValues vals;
+  protected String field;
+  protected long currentValue;
+  protected LongComp comp;
+  private int lastDocID;
+
+  public LongValue(String field, LongComp comp) {
+    this.field = field;
+    this.comp = comp;
+    this.currentValue = comp.resetValue();
+  }
+
+  public LongValue copy() {
+    return new LongValue(field, comp);
+  }
+
+  public void setNextReader(LeafReaderContext context) throws IOException {
+    this.vals = DocValues.getNumeric(context.reader(), field);
+    lastDocID = 0;
+  }
+
+  public void setCurrentValue(int docId) throws IOException {
+    if (docId < lastDocID) {
+      throw new AssertionError("docs were sent out-of-order: lastDocID=" + lastDocID + " vs doc=" + docId);
+    }
+    lastDocID = docId;
+    int curDocID = vals.docID();
+    if (docId > curDocID) {
+      curDocID = vals.advance(docId);
+    }
+    if (docId == curDocID) {
+      currentValue = vals.longValue();
+    } else {
+      currentValue = 0;
+    }
+  }
+
+  public void setCurrentValue(SortValue sv) {
+    LongValue lv = (LongValue)sv;
+    this.currentValue = lv.currentValue;
+  }
+
+  public int compareTo(SortValue o) {
+    LongValue l = (LongValue)o;
+    return comp.compare(currentValue, l.currentValue);
+  }
+
+  public void reset() {
+    this.currentValue = comp.resetValue();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/MultiFieldWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/MultiFieldWriter.java b/solr/core/src/java/org/apache/solr/handler/export/MultiFieldWriter.java
new file mode 100644
index 0000000..2e43207
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/MultiFieldWriter.java
@@ -0,0 +1,104 @@
+/*
+ * 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.handler.export;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.function.LongFunction;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.index.SortedSetDocValues;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.CharsRefBuilder;
+import org.apache.lucene.util.NumericUtils;
+import org.apache.solr.common.IteratorWriter;
+import org.apache.solr.common.MapWriter;
+import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.SchemaField;
+
+class MultiFieldWriter extends FieldWriter {
+  private String field;
+  private FieldType fieldType;
+  private SchemaField schemaField;
+  private boolean numeric;
+  private CharsRefBuilder cref = new CharsRefBuilder();
+  private final LongFunction<Object> bitsToValue;
+
+  public MultiFieldWriter(String field, FieldType fieldType, SchemaField schemaField, boolean numeric) {
+    this.field = field;
+    this.fieldType = fieldType;
+    this.schemaField = schemaField;
+    this.numeric = numeric;
+    if (this.fieldType.isPointField()) {
+      bitsToValue = bitsToValue(fieldType);
+    } else {
+      bitsToValue = null;
+    }
+  }
+
+  public boolean write(int docId, LeafReader reader, MapWriter.EntryWriter out, int fieldIndex) throws IOException {
+    if (this.fieldType.isPointField()) {
+      SortedNumericDocValues vals = DocValues.getSortedNumeric(reader, this.field);
+      if (!vals.advanceExact(docId)) return false;
+      out.put(this.field,
+          (IteratorWriter) w -> {
+            for (int i = 0; i < vals.docValueCount(); i++) {
+              w.add(bitsToValue.apply(vals.nextValue()));
+            }
+          });
+      return true;
+    } else {
+      SortedSetDocValues vals = DocValues.getSortedSet(reader, this.field);
+      if (vals.advance(docId) != docId) return false;
+      out.put(this.field,
+          (IteratorWriter) w -> {
+            long o;
+            while((o = vals.nextOrd()) != SortedSetDocValues.NO_MORE_ORDS) {
+              BytesRef ref = vals.lookupOrd(o);
+              fieldType.indexedToReadable(ref, cref);
+              IndexableField f = fieldType.createField(schemaField, cref.toString());
+              if (f == null) w.add(cref.toString());
+              else w.add(fieldType.toObject(f));
+            }
+          });
+      return true;
+    }
+
+  }
+
+
+  static LongFunction<Object> bitsToValue(FieldType fieldType) {
+    switch (fieldType.getNumberType()) {
+      case LONG:
+        return (bits)-> bits;
+      case DATE:
+        return (bits)-> new Date(bits);
+      case INTEGER:
+        return (bits)-> (int)bits;
+      case FLOAT:
+        return (bits)-> NumericUtils.sortableIntToFloat((int)bits);
+      case DOUBLE:
+        return (bits)-> NumericUtils.sortableLongToDouble(bits);
+      default:
+        throw new AssertionError("Unsupported NumberType: " + fieldType.getNumberType());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/PriorityQueue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/PriorityQueue.java b/solr/core/src/java/org/apache/solr/handler/export/PriorityQueue.java
new file mode 100644
index 0000000..1552060
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/PriorityQueue.java
@@ -0,0 +1,218 @@
+/*
+ * 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.handler.export;
+
+import org.apache.lucene.util.ArrayUtil;
+
+public abstract class PriorityQueue<T> {
+  protected int size = 0;
+  protected final int maxSize;
+  private final T[] heap;
+
+  public PriorityQueue(int maxSize) {
+    this(maxSize, true);
+  }
+
+  public PriorityQueue(int maxSize, boolean prepopulate) {
+    final int heapSize;
+    if (0 == maxSize) {
+      // We allocate 1 extra to avoid if statement in top()
+      heapSize = 2;
+    } else {
+      if (maxSize > ArrayUtil.MAX_ARRAY_LENGTH) {
+        // Don't wrap heapSize to -1, in this case, which
+        // causes a confusing NegativeArraySizeException.
+        // Note that very likely this will simply then hit
+        // an OOME, but at least that's more indicative to
+        // caller that this values is too big.  We don't +1
+        // in this case, but it's very unlikely in practice
+        // one will actually insert this many objects into
+        // the PQ:
+        // Throw exception to prevent confusing OOME:
+        throw new IllegalArgumentException("maxSize must be <= " + ArrayUtil.MAX_ARRAY_LENGTH + "; got: " + maxSize);
+      } else {
+        // NOTE: we add +1 because all access to heap is
+        // 1-based not 0-based.  heap[0] is unused.
+        heapSize = maxSize + 1;
+      }
+    }
+    // T is unbounded type, so this unchecked cast works always:
+    @SuppressWarnings("unchecked") final T[] h = (T[]) new Object[heapSize];
+    this.heap = h;
+    this.maxSize = maxSize;
+
+    if (prepopulate) {
+      // If sentinel objects are supported, populate the queue with them
+      T sentinel = getSentinelObject();
+      if (sentinel != null) {
+        heap[1] = sentinel;
+        for (int i = 2; i < heap.length; i++) {
+          heap[i] = getSentinelObject();
+        }
+        size = maxSize;
+      }
+    }
+  }
+
+  /** Determines the ordering of objects in this priority queue.  Subclasses
+   *  must define this one method.
+   *  @return <code>true</code> iff parameter <tt>a</tt> is less than parameter <tt>b</tt>.
+   */
+  protected abstract boolean lessThan(T a, T b);
+
+
+  protected T getSentinelObject() {
+    return null;
+  }
+
+  /**
+   * Adds an Object to a PriorityQueue in log(size) time. If one tries to add
+   * more objects than maxSize from initialize an
+   *
+   * @return the new 'top' element in the queue.
+   */
+  public final T add(T element) {
+    size++;
+    heap[size] = element;
+    upHeap();
+    return heap[1];
+  }
+
+  /**
+   * Adds an Object to a PriorityQueue in log(size) time.
+   * It returns the object (if any) that was
+   * dropped off the heap because it was full. This can be
+   * the given parameter (in case it is smaller than the
+   * full heap's minimum, and couldn't be added), or another
+   * object that was previously the smallest value in the
+   * heap and now has been replaced by a larger one, or null
+   * if the queue wasn't yet full with maxSize elements.
+   */
+  public T insertWithOverflow(T element) {
+    if (size < maxSize) {
+      add(element);
+      return null;
+    } else if (size > 0 && !lessThan(element, heap[1])) {
+      T ret = heap[1];
+      heap[1] = element;
+      updateTop();
+      return ret;
+    } else {
+      return element;
+    }
+  }
+
+  /** Returns the least element of the PriorityQueue in constant time. */
+  public final T top() {
+    // We don't need to check size here: if maxSize is 0,
+    // then heap is length 2 array with both entries null.
+    // If size is 0 then heap[1] is already null.
+    return heap[1];
+  }
+
+  /** Removes and returns the least element of the PriorityQueue in log(size)
+   time. */
+  public final T pop() {
+    if (size > 0) {
+      T result = heap[1];       // save first value
+      heap[1] = heap[size];     // move last to first
+      heap[size] = null;        // permit GC of objects
+      size--;
+      downHeap();               // adjust heap
+      return result;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Should be called when the Object at top changes values. Still log(n) worst
+   * case, but it's at least twice as fast to
+   *
+   * <pre class="prettyprint">
+   * pq.top().change();
+   * pq.updateTop();
+   * </pre>
+   *
+   * instead of
+   *
+   * <pre class="prettyprint">
+   * o = pq.pop();
+   * o.change();
+   * pq.push(o);
+   * </pre>
+   *
+   * @return the new 'top' element.
+   */
+  public final T updateTop() {
+    downHeap();
+    return heap[1];
+  }
+
+  /** Returns the number of elements currently stored in the PriorityQueue. */
+  public final int size() {
+    return size;
+  }
+
+  /** Removes all entries from the PriorityQueue. */
+  public final void clear() {
+    for (int i = 0; i <= size; i++) {
+      heap[i] = null;
+    }
+    size = 0;
+  }
+
+  private final void upHeap() {
+    int i = size;
+    T node = heap[i];          // save bottom node
+    int j = i >>> 1;
+    while (j > 0 && lessThan(node, heap[j])) {
+      heap[i] = heap[j];       // shift parents down
+      i = j;
+      j = j >>> 1;
+    }
+    heap[i] = node;            // install saved node
+  }
+
+  private final void downHeap() {
+    int i = 1;
+    T node = heap[i];          // save top node
+    int j = i << 1;            // find smaller child
+    int k = j + 1;
+    if (k <= size && lessThan(heap[k], heap[j])) {
+      j = k;
+    }
+    while (j <= size && lessThan(heap[j], node)) {
+      heap[i] = heap[j];       // shift up child
+      i = j;
+      j = i << 1;
+      k = j + 1;
+      if (k <= size && lessThan(heap[k], heap[j])) {
+        j = k;
+      }
+    }
+    heap[i] = node;            // install saved node
+  }
+
+  /** This method returns the internal heap array as Object[].
+   * @lucene.internal
+   */
+  public final Object[] getHeapArray() {
+    return (Object[]) heap;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/SortDoc.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/SortDoc.java b/solr/core/src/java/org/apache/solr/handler/export/SortDoc.java
new file mode 100644
index 0000000..3cf478b
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/SortDoc.java
@@ -0,0 +1,114 @@
+/*
+ * 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.handler.export;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.LeafReaderContext;
+
+class SortDoc {
+
+  protected int docId = -1;
+  protected int ord = -1;
+  protected int docBase = -1;
+
+  private SortValue[] sortValues;
+
+  public SortDoc(SortValue[] sortValues) {
+    this.sortValues = sortValues;
+  }
+
+  public void setNextReader(LeafReaderContext context) throws IOException {
+    this.ord = context.ord;
+    this.docBase = context.docBase;
+    for (SortValue value : sortValues) {
+      value.setNextReader(context);
+    }
+  }
+
+  public void reset() {
+    this.docId = -1;
+    for (SortValue value : sortValues) {
+      value.reset();
+    }
+  }
+
+  public void setValues(int docId) throws IOException {
+    this.docId = docId;
+    for(SortValue sortValue : sortValues) {
+      sortValue.setCurrentValue(docId);
+    }
+  }
+
+  public void setValues(SortDoc sortDoc) {
+    this.docId = sortDoc.docId;
+    this.ord = sortDoc.ord;
+    this.docBase = sortDoc.docBase;
+    SortValue[] vals = sortDoc.sortValues;
+    for(int i=0; i<vals.length; i++) {
+      sortValues[i].setCurrentValue(vals[i]);
+    }
+  }
+
+  public SortDoc copy() {
+    SortValue[] svs = new SortValue[sortValues.length];
+    for(int i=0; i<sortValues.length; i++) {
+      svs[i] = sortValues[i].copy();
+    }
+
+    return new SortDoc(svs);
+  }
+
+  public boolean lessThan(Object o) {
+    if(docId == -1) {
+      return true;
+    }
+    SortDoc sd = (SortDoc)o;
+    SortValue[] sortValues1 = sd.sortValues;
+    for(int i=0; i<sortValues.length; i++) {
+      int comp = sortValues[i].compareTo(sortValues1[i]);
+      if(comp < 0) {
+        return true;
+      } if(comp > 0) {
+        return false;
+      }
+    }
+    return docId+docBase > sd.docId+sd.docBase; //index order
+  }
+
+  public int compareTo(Object o) {
+    SortDoc sd = (SortDoc)o;
+    for (int i=0; i<sortValues.length; i++) {
+      int comp = sortValues[i].compareTo(sd.sortValues[i]);
+      if (comp != 0) {
+        return comp;
+      }
+    }
+    return 0;
+  }
+
+
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    builder.append("docId: " + docId + "; ");
+    for (int i=0; i < sortValues.length; i++) {
+      builder.append("value" + i + ": " + sortValues[i] + ", ");
+    }
+    return builder.toString();
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/SortQueue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/SortQueue.java b/solr/core/src/java/org/apache/solr/handler/export/SortQueue.java
new file mode 100644
index 0000000..cf99d53
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/SortQueue.java
@@ -0,0 +1,52 @@
+/*
+ * 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.handler.export;
+
+class SortQueue extends PriorityQueue<SortDoc> {
+
+  private SortDoc proto;
+  private Object[] cache;
+
+  public SortQueue(int len, SortDoc proto) {
+    super(len);
+    this.proto = proto;
+  }
+
+  protected boolean lessThan(SortDoc t1, SortDoc t2) {
+    return t1.lessThan(t2);
+  }
+
+  protected void populate() {
+    Object[] heap = getHeapArray();
+    cache = new SortDoc[heap.length];
+    for (int i = 1; i < heap.length; i++) {
+      cache[i] = heap[i] = proto.copy();
+    }
+    size = maxSize;
+  }
+
+  protected void reset() {
+    Object[] heap = getHeapArray();
+    if(cache != null) {
+      System.arraycopy(cache, 1, heap, 1, heap.length-1);
+      size = maxSize;
+    } else {
+      populate();
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/SortValue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/SortValue.java b/solr/core/src/java/org/apache/solr/handler/export/SortValue.java
new file mode 100644
index 0000000..98afd0a
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/SortValue.java
@@ -0,0 +1,30 @@
+/*
+ * 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.handler.export;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.LeafReaderContext;
+
+public interface SortValue extends Comparable<SortValue> {
+  public void setCurrentValue(int docId) throws IOException;
+  public void setNextReader(LeafReaderContext context) throws IOException;
+  public void setCurrentValue(SortValue value);
+  public void reset();
+  public SortValue copy();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/StringFieldWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/StringFieldWriter.java b/solr/core/src/java/org/apache/solr/handler/export/StringFieldWriter.java
new file mode 100644
index 0000000..9008f71
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/StringFieldWriter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.handler.export;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.SortedDocValues;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.CharsRefBuilder;
+import org.apache.solr.common.MapWriter;
+import org.apache.solr.schema.FieldType;
+
+class StringFieldWriter extends FieldWriter {
+  private String field;
+  private FieldType fieldType;
+  private CharsRefBuilder cref = new CharsRefBuilder();
+
+  public StringFieldWriter(String field, FieldType fieldType) {
+    this.field = field;
+    this.fieldType = fieldType;
+  }
+
+  public boolean write(int docId, LeafReader reader, MapWriter.EntryWriter ew, int fieldIndex) throws IOException {
+    SortedDocValues vals = DocValues.getSorted(reader, this.field);
+    if (vals.advance(docId) != docId) {
+      return false;
+    }
+    int ord = vals.ordValue();
+
+    BytesRef ref = vals.lookupOrd(ord);
+    fieldType.indexedToReadable(ref, cref);
+    ew.put(this.field, cref.toString());
+    return true;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/StringValue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/StringValue.java b/solr/core/src/java/org/apache/solr/handler/export/StringValue.java
new file mode 100644
index 0000000..23b1413
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/StringValue.java
@@ -0,0 +1,99 @@
+/*
+ * 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.handler.export;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.MultiDocValues;
+import org.apache.lucene.index.OrdinalMap;
+import org.apache.lucene.index.SortedDocValues;
+import org.apache.lucene.util.LongValues;
+
+class StringValue implements SortValue {
+
+  protected SortedDocValues globalDocValues;
+
+  protected OrdinalMap ordinalMap;
+  protected LongValues toGlobal = LongValues.IDENTITY; // this segment to global ordinal. NN;
+  protected SortedDocValues docValues;
+
+  protected String field;
+  protected int currentOrd;
+  protected IntComp comp;
+  protected int lastDocID;
+
+  public StringValue(SortedDocValues globalDocValues, String field, IntComp comp)  {
+    this.globalDocValues = globalDocValues;
+    this.docValues = globalDocValues;
+    if (globalDocValues instanceof MultiDocValues.MultiSortedDocValues) {
+      this.ordinalMap = ((MultiDocValues.MultiSortedDocValues) globalDocValues).mapping;
+    }
+    this.field = field;
+    this.comp = comp;
+    this.currentOrd = comp.resetValue();
+  }
+
+  public StringValue copy() {
+    return new StringValue(globalDocValues, field, comp);
+  }
+
+  public void setCurrentValue(int docId) throws IOException {
+    if (docId < lastDocID) {
+      throw new AssertionError("docs were sent out-of-order: lastDocID=" + lastDocID + " vs doc=" + docId);
+    }
+
+    lastDocID = docId;
+
+    if (docId > docValues.docID()) {
+      docValues.advance(docId);
+    }
+    if (docId == docValues.docID()) {
+      currentOrd = (int) toGlobal.get(docValues.ordValue());
+    } else {
+      currentOrd = -1;
+    }
+  }
+
+  public void setCurrentValue(SortValue sv) {
+    StringValue v = (StringValue)sv;
+    this.currentOrd = v.currentOrd;
+  }
+
+  public void setNextReader(LeafReaderContext context) throws IOException {
+    if (globalDocValues instanceof MultiDocValues.MultiSortedDocValues) {
+      toGlobal = ordinalMap.getGlobalOrds(context.ord);
+      docValues = DocValues.getSorted(context.reader(), field);
+    }
+    lastDocID = 0;
+  }
+
+  public void reset() {
+    this.currentOrd = comp.resetValue();
+  }
+
+  public int compareTo(SortValue o) {
+    StringValue sv = (StringValue)o;
+    return comp.compare(currentOrd, sv.currentOrd);
+  }
+
+  public String toString() {
+    return Integer.toString(this.currentOrd);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/java/org/apache/solr/handler/export/package-info.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/export/package-info.java b/solr/core/src/java/org/apache/solr/handler/export/package-info.java
new file mode 100644
index 0000000..a516904
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/export/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Solr's Export Handler Functionality
+ */
+package org.apache.solr.handler.export;
+
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/test-files/solr/collection1/conf/schema-sortingresponse.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema-sortingresponse.xml b/solr/core/src/test-files/solr/collection1/conf/schema-sortingresponse.xml
index c45f178..57e684d 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema-sortingresponse.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema-sortingresponse.xml
@@ -40,7 +40,7 @@
 
   <fieldType name="uuid" class="solr.UUIDField"/>
 
-  <field name="id" type="string" required="true" indexed="true"/>
+  <field name="id" type="string" required="true" indexed="true" docValues="true"/>
   <field name="floatdv_m" type="float" indexed="false" stored="false" docValues="true" multiValued="true"/>
   <field name="intdv_m" type="int" indexed="false" stored="false" docValues="true" multiValued="true"/>
   <field name="doubledv_m" type="double" indexed="false" stored="false" docValues="true" multiValued="true"/>
@@ -54,6 +54,9 @@
   <field name="longdv" type="long" indexed="false" stored="false" docValues="true"/>
   <field name="datedv" type="date" indexed="false" stored="false" docValues="true"/>
   <field name="stringdv" type="string" indexed="false" stored="false" docValues="true"/>
+  <field name="booleandv" type="boolean" indexed="false" stored="false" docValues="true" />
+
+  <dynamicField name="*_s_dv"   type="string"    indexed="true"  stored="true" docValues="true" multiValued="false"/>
 
    <!-- Point fields explicitly -->
    <dynamicField name="*_i_p"      type="pint"    indexed="true"  stored="true" docValues="true" multiValued="false"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/9d9c3a0c/solr/core/src/test/org/apache/solr/handler/export/TestExportWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/export/TestExportWriter.java b/solr/core/src/test/org/apache/solr/handler/export/TestExportWriter.java
new file mode 100644
index 0000000..55efdee
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/export/TestExportWriter.java
@@ -0,0 +1,749 @@
+/*
+ * 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.handler.export;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.lucene.util.TestUtil;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.util.SuppressForbidden;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.ResultContext;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.DocIterator;
+import org.apache.solr.search.DocList;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestExportWriter extends SolrTestCaseJ4 {
+  
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    System.setProperty("export.test", "true");
+    initCore("solrconfig-sortingresponse.xml","schema-sortingresponse.xml");
+  }
+
+  @Before
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    assertU(delQ("*:*"));
+    assertU(commit());
+    createIndex();
+  }
+
+  public static void createIndex() {
+    assertU(adoc("id","1",
+                 "floatdv","2.1",
+                 "intdv", "1",
+                 "stringdv", "hello world",
+                 "longdv", "323223232323",
+                 "doubledv","2344.345",
+                 "intdv_m","100",
+                 "intdv_m","250",
+                 "floatdv_m", "123.321",
+                 "floatdv_m", "345.123",
+                 "doubledv_m", "3444.222",
+                 "doubledv_m", "23232.2",
+                 "longdv_m", "43434343434",
+                 "longdv_m", "343332",
+                 "stringdv_m", "manchester \"city\"",
+                 "stringdv_m", "liverpool",
+                 "stringdv_m", "Everton",
+                 "datedv", "2017-06-16T07:00:00Z",
+                 "datedv_m", "2017-06-16T01:00:00Z",
+                 "datedv_m", "2017-06-16T02:00:00Z",
+                 "datedv_m", "2017-06-16T03:00:00Z",
+                 "datedv_m", "2017-06-16T04:00:00Z"));
+
+    assertU(adoc("id","7",
+        "floatdv","2.1",
+        "intdv", "7",
+        "longdv", "323223232323",
+        "doubledv","2344.345",
+        "floatdv_m", "123.321",
+        "floatdv_m", "345.123",
+        "doubledv_m", "3444.222",
+        "doubledv_m", "23232.2",
+        "longdv_m", "43434343434",
+        "longdv_m", "343332"));
+
+    assertU(commit());
+    assertU(adoc("id","2", "floatdv","2.1", "intdv", "2", "stringdv", "hello world", "longdv", "323223232323","doubledv","2344.344"));
+    assertU(commit());
+    assertU(adoc("id","3",
+        "floatdv","2.1",
+        "intdv", "3",
+        "stringdv", "chello world",
+        "longdv", "323223232323",
+        "doubledv","2344.346",
+        "intdv_m","100",
+        "intdv_m","250",
+        "floatdv_m", "123.321",
+        "floatdv_m", "345.123",
+        "doubledv_m", "3444.222",
+        "doubledv_m", "23232.2",
+        "longdv_m", "43434343434",
+        "longdv_m", "343332",
+        "stringdv_m", "manchester \"city\"",
+        "stringdv_m", "liverpool",
+        "stringdv_m", "everton",
+        "int_is_t", "1",
+        "int_is_t", "1",
+        "int_is_t", "1",
+        "int_is_t", "1"));
+    assertU(commit());
+    assertU(adoc("id","8",
+        "floatdv","2.1",
+        "intdv", "10000000",
+        "stringdv", "chello \"world\"",
+        "longdv", "323223232323",
+        "doubledv","2344.346",
+        "intdv_m","100",
+        "intdv_m","250",
+        "floatdv_m", "123.321",
+        "floatdv_m", "345.123",
+        "doubledv_m", "3444.222",
+        "doubledv_m", "23232.2",
+        "longdv_m", "43434343434",
+        "longdv_m", "343332",
+        "stringdv_m", "manchester \"city\"",
+        "stringdv_m", "liverpool",
+        "stringdv_m", "everton",
+        "datedv", "2017-01-01T00:00:00Z",
+        "datedv_m", "2017-01-01T01:00:00Z",
+        "datedv_m", "2017-01-01T02:00:00Z",
+        "int_is_p", "1",
+        "int_is_p", "1",
+        "int_is_p", "1",
+        "int_is_p", "1"));
+    assertU(commit());
+
+
+  }
+
+  @Test
+  public void testSmallChains() throws Exception {
+    clearIndex();
+
+    assertU(adoc("id","1",
+        "field1_i_p",Integer.toString(Integer.MIN_VALUE),
+        "field2_i_p","1"));
+    assertU(commit());
+
+    assertU(adoc("id","2",
+        "field1_i_p",Integer.toString(Integer.MIN_VALUE),
+        "field2_i_p",Integer.toString(Integer.MIN_VALUE + 1)));
+    assertU(commit());
+
+    assertU(adoc("id","3",
+        "field1_i_p",Integer.toString(Integer.MIN_VALUE),
+        "field2_i_p",Integer.toString(Integer.MIN_VALUE)));
+    assertU(commit());
+
+    //Test single value DocValue output
+    //Expected for asc sort doc3 -> doc2 -> doc1
+    String s =  h.query(req("q", "*:*", "qt", "/export", "fl", "id", "sort", "field1_i_p asc,field2_i_p asc"));
+    assertJsonEquals(s, "{\n" +
+        "  \"responseHeader\":{\"status\":0},\n" +
+        "  \"response\":{\n" +
+        "    \"numFound\":3,\n" +
+        "    \"docs\":[{\n" +
+        "        \"id\":\"3\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"2\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"1\"}]}}");
+
+    clearIndex();
+    //Adding 3 docs of integers with the following values
+    //  doc1: Integer.MIN_VALUE,1,2,Integer.MAX_VALUE,3,4,5,6
+    //  doc2: Integer.MIN_VALUE,Integer.MIN_VALUE,2,Integer.MAX_VALUE,4,4,5,6
+    //  doc3: Integer.MIN_VALUE,Integer.MIN_VALUE,2,Integer.MAX_VALUE,3,4,5,6
+
+    assertU(adoc("id","1",
+        "field1_i_p",Integer.toString(Integer.MIN_VALUE),
+        "field2_i_p","1",
+        "field3_i_p","2",
+        "field4_i_p",Integer.toString(Integer.MAX_VALUE),
+        "field5_i_p","3",
+        "field6_i_p","4",
+        "field7_i_p","5",
+        "field8_i_p","6"));
+    assertU(commit());
+
+    assertU(adoc("id","2",
+        "field1_i_p",Integer.toString(Integer.MIN_VALUE),
+        "field2_i_p",Integer.toString(Integer.MIN_VALUE),
+        "field3_i_p","2",
+        "field4_i_p",Integer.toString(Integer.MAX_VALUE),
+        "field5_i_p","4",
+        "field6_i_p","4",
+        "field7_i_p","5",
+        "field8_i_p","6"));
+    assertU(commit());
+
+    assertU(adoc("id","3",
+        "field1_i_p",Integer.toString(Integer.MIN_VALUE),
+        "field2_i_p",Integer.toString(Integer.MIN_VALUE),
+        "field3_i_p","2",
+        "field4_i_p",Integer.toString(Integer.MAX_VALUE),
+        "field5_i_p","3",
+        "field6_i_p","4",
+        "field7_i_p","5",
+        "field8_i_p","6"));
+    assertU(commit());
+
+    s =  h.query(req("q", "*:*", "qt", "/export", "fl", "id", "sort", "field1_i_p asc,field2_i_p asc,field3_i_p asc,field4_i_p asc,field5_i_p desc,field6_i_p desc,field7_i_p desc,field8_i_p asc"));
+    assertJsonEquals(s, "{\n" +
+        "  \"responseHeader\":{\"status\":0},\n" +
+        "  \"response\":{\n" +
+        "    \"numFound\":3,\n" +
+        "    \"docs\":[{\n" +
+        "        \"id\":\"2\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"3\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"1\"}]}}");
+
+  }
+
+  @Test
+  public void testIndexOrder() throws Exception {
+    clearIndex();
+
+    assertU(adoc("id","1", "stringdv","a"));
+    assertU(adoc("id","2", "stringdv","a"));
+
+    assertU(commit());
+
+    assertU(adoc("id","3", "stringdv","a"));
+    assertU(adoc("id","4", "stringdv","a"));
+
+    assertU(commit());
+
+    String expectedResult = "{\n" +
+        "  \"responseHeader\":{\"status\":0},\n" +
+        "  \"response\":{\n" +
+        "    \"numFound\":4,\n" +
+        "    \"docs\":[{\n" +
+        "        \"id\":\"1\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"2\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"3\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"4\"}]}}";
+
+    String s =  h.query(req("q", "*:*", "qt", "/export", "fl", "id", "sort", "stringdv asc"));
+    assertJsonEquals(s, expectedResult);
+
+    s =  h.query(req("q", "*:*", "qt", "/export", "fl", "id", "sort", "stringdv desc"));
+    assertJsonEquals(s, expectedResult);
+
+  }
+
+  @Test
+  public void testStringWithCase() throws Exception {
+    clearIndex();
+
+    assertU(adoc("id","1", "stringdv","a"));
+    assertU(adoc("id","2", "stringdv","ABC"));
+
+    assertU(commit());
+
+    assertU(adoc("id","3", "stringdv","xyz"));
+    assertU(adoc("id","4", "stringdv","a"));
+
+    assertU(commit());
+
+    String s =  h.query(req("q", "*:*", "qt", "/export", "fl", "id", "sort", "stringdv desc"));
+    assertJsonEquals(s, "{\n" +
+        "  \"responseHeader\":{\"status\":0},\n" +
+        "  \"response\":{\n" +
+        "    \"numFound\":4,\n" +
+        "    \"docs\":[{\n" +
+        "        \"id\":\"3\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"1\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"4\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"2\"}]}}");
+  }
+
+  @Test
+  public void testBooleanField() throws Exception {
+    clearIndex();
+
+    assertU(adoc("id","1",
+        "booleandv","true"));
+    assertU(commit());
+
+    assertU(adoc("id","2",
+        "booleandv","false"));
+    assertU(commit());
+
+    String s =  h.query(req("q", "*:*", "qt", "/export", "fl", "id", "sort", "booleandv asc"));
+    assertJsonEquals(s, "{\n" +
+        "  \"responseHeader\":{\"status\":0},\n" +
+        "  \"response\":{\n" +
+        "    \"numFound\":2,\n" +
+        "    \"docs\":[{\n" +
+        "        \"id\":\"2\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"1\"}]}}");
+
+    s =  h.query(req("q", "*:*", "qt", "/export", "fl", "id", "sort", "booleandv desc"));
+    assertJsonEquals(s, "{\n" +
+        "  \"responseHeader\":{\"status\":0},\n" +
+        "  \"response\":{\n" +
+        "    \"numFound\":2,\n" +
+        "    \"docs\":[{\n" +
+        "        \"id\":\"1\"}\n" +
+        "      ,{\n" +
+        "        \"id\":\"2\"}]}}");
+  }
+
+  @Test
+  public void testSortingOutput() throws Exception {
+
+    //Test single value DocValue output
+    String s =  h.query(req("q", "id:1", "qt", "/export", "fl", "floatdv,intdv,stringdv,longdv,doubledv", "sort", "intdv asc"));
+
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":1, \"docs\":[{\"floatdv\":2.1,\"intdv\":1,\"stringdv\":\"hello world\",\"longdv\":323223232323,\"doubledv\":2344.345}]}}");
+
+    //Test null value string:
+    s = h.query(req("q", "id:7", "qt", "/export", "fl", "floatdv,intdv,stringdv,longdv,doubledv", "sort", "intdv asc"));
+
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":1, \"docs\":[{\"floatdv\":2.1,\"intdv\":7,\"longdv\":323223232323,\"doubledv\":2344.345}]}}");
+
+    //Test multiValue docValues output
+    s = h.query(req("q", "id:1", "qt", "/export", "fl", "intdv_m,floatdv_m,doubledv_m,longdv_m,stringdv_m", "sort", "intdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":1, \"docs\":[{\"intdv_m\":[100,250],\"floatdv_m\":[123.321,345.123],\"doubledv_m\":[3444.222,23232.2],\"longdv_m\":[343332,43434343434],\"stringdv_m\":[\"Everton\",\"liverpool\",\"manchester \\\"city\\\"\"]}]}}");
+
+    //Test multiValues docValues output with nulls
+    s =  h.query(req("q", "id:7", "qt", "/export", "fl", "intdv_m,floatdv_m,doubledv_m,longdv_m,stringdv_m", "sort", "intdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":1, \"docs\":[{\"floatdv_m\":[123.321,345.123],\"doubledv_m\":[3444.222,23232.2],\"longdv_m\":[343332,43434343434]}]}}");
+
+    //Test single sort param is working
+    s =  h.query(req("q", "id:(1 2)", "qt", "/export", "fl", "intdv", "sort", "intdv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":2, \"docs\":[{\"intdv\":2},{\"intdv\":1}]}}");
+
+    s =  h.query(req("q", "id:(1 2)", "qt", "/export", "fl", "intdv", "sort", "intdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":2, \"docs\":[{\"intdv\":1},{\"intdv\":2}]}}");
+
+    // Test sort on String will null value. Null value should sort last on desc and first on asc.
+
+    s =  h.query(req("q", "id:(1 7)", "qt", "/export", "fl", "intdv", "sort", "stringdv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":2, \"docs\":[{\"intdv\":1},{\"intdv\":7}]}}");
+
+    s =  h.query(req("q", "id:(1 7)", "qt", "/export", "fl", "intdv", "sort", "stringdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":2, \"docs\":[{\"intdv\":7},{\"intdv\":1}]}}");
+
+
+    //Test multi-sort params
+    s =  h.query(req("q", "id:(1 2)", "qt", "/export", "fl", "intdv", "sort", "floatdv asc,intdv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":2, \"docs\":[{\"intdv\":2},{\"intdv\":1}]}}");
+
+    s =  h.query(req("q", "id:(1 2)", "qt", "/export", "fl", "intdv", "sort", "floatdv desc,intdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":2, \"docs\":[{\"intdv\":1},{\"intdv\":2}]}}");
+
+    //Test three sort fields
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "floatdv asc,stringdv asc,intdv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":3},{\"intdv\":2},{\"intdv\":1}]}}");
+
+    //Test three sort fields
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "floatdv asc,stringdv desc,intdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":1},{\"intdv\":2},{\"intdv\":3}]}}");
+
+    //Test four sort fields
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "floatdv asc,floatdv desc,floatdv asc,intdv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":3},{\"intdv\":2},{\"intdv\":1}]}}");
+
+    //Test five sort fields
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "intdv desc,floatdv asc,floatdv desc,floatdv asc,intdv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":3},{\"intdv\":2},{\"intdv\":1}]}}");
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "floatdv desc,intdv asc,floatdv desc,floatdv desc,intdv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":1},{\"intdv\":2},{\"intdv\":3}]}}");
+
+    //Test six sort fields
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "floatdv asc,intdv desc,floatdv asc,floatdv desc,floatdv asc,intdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":3},{\"intdv\":2},{\"intdv\":1}]}}");
+
+    //Test seven sort fields
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "floatdv desc,intdv asc,floatdv desc,floatdv asc,floatdv desc,floatdv asc,intdv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":1},{\"intdv\":2},{\"intdv\":3}]}}");
+
+    //Test eight sort fields
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "floatdv asc,intdv desc,floatdv asc,floatdv desc,floatdv asc,floatdv desc,floatdv asc,intdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":3},{\"intdv\":2},{\"intdv\":1}]}}");
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "intdv asc,floatdv desc,floatdv asc,floatdv desc,floatdv asc,floatdv desc,floatdv asc,intdv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":1},{\"intdv\":2},{\"intdv\":3}]}}");
+
+    //Test nine sort fields
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "intdv asc,floatdv desc,floatdv asc,floatdv desc,floatdv asc,floatdv desc,intdv asc,intdv desc,floatdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":1},{\"intdv\":2},{\"intdv\":3}]}}");
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "floatdv asc,intdv desc,floatdv asc,floatdv desc,floatdv asc,floatdv desc,intdv desc,intdv asc,floatdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":3},{\"intdv\":2},{\"intdv\":1}]}}");
+
+    //Test ten sort fields
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "intdv asc,floatdv desc,floatdv asc,floatdv desc,floatdv asc,floatdv desc,intdv asc,intdv desc,floatdv desc,floatdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":1},{\"intdv\":2},{\"intdv\":3}]}}");
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "floatdv asc,intdv desc,floatdv asc,floatdv desc,floatdv asc,floatdv desc,intdv desc,intdv asc,floatdv desc,floatdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":3},{\"intdv\":2},{\"intdv\":1}]}}");
+
+    s =  h.query(req("q", "id:(1 2 3)", "qt", "/export", "fl", "intdv", "sort", "doubledv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":3},{\"intdv\":1},{\"intdv\":2}]}}");
+
+    s =  h.query(req("q", "intdv:[2 TO 1000]", "qt", "/export", "fl", "intdv", "sort", "doubledv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":3, \"docs\":[{\"intdv\":3},{\"intdv\":7},{\"intdv\":2}]}}");
+
+    s =  h.query(req("q", "stringdv:blah", "qt", "/export", "fl", "intdv", "sort", "doubledv desc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":0, \"docs\":[]}}");
+
+    s =  h.query(req("q", "id:8", "qt", "/export", "fl", "stringdv", "sort", "intdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":1, \"docs\":[{\"stringdv\":\"chello \\\"world\\\"\"}]}}");
+  }
+
+  private void assertJsonEquals(String actual, String expected) {
+    assertEquals(Utils.toJSONString(Utils.fromJSONString(expected)), Utils.toJSONString(Utils.fromJSONString(actual)));
+  }
+
+  @Test
+  public void testExportRequiredParams() throws Exception {
+
+    //Test whether missing required parameters returns expected errors.
+
+    //String s =  h.query(req("q", "id:1", "qt", "/export", "fl", "floatdv,intdv,stringdv,longdv,doubledv", "sort", "intdv asc"));
+    String s;
+    s = h.query(req("qt", "/export"));
+    assertTrue("Should have had a sort error", s.contains("No sort criteria"));
+    s = h.query(req("sort", "intdv asc", "qt", "/export"));
+    assertTrue("Should have had fl error", s.contains("export field list (fl) must be specified"));
+    s = h.query(req("sort", "intdv asc", "qt", "/export", "fl", "stringdv"));
+    // Interesting you don't even need to specify a "q" parameter.
+    
+  }
+  
+  @Test
+  public void testDates() throws Exception {
+    String s =  h.query(req("q", "id:1", "qt", "/export", "fl", "datedv", "sort", "datedv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":1, \"docs\":[{\"datedv\":\"2017-06-16T07:00:00Z\"}]}}");
+    s =  h.query(req("q", "id:1", "qt", "/export", "fl", "datedv_m", "sort", "datedv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":1, \"docs\":[{\"datedv_m\":[\"2017-06-16T01:00:00Z\",\"2017-06-16T02:00:00Z\",\"2017-06-16T03:00:00Z\",\"2017-06-16T04:00:00Z\"]}]}}");
+  }
+  
+  @Test
+  public void testDuplicates() throws Exception {
+    // see SOLR-10924
+    String expected = h.getCore().getLatestSchema().getField("int_is_t").getType().isPointField()
+      ? "1,1,1,1" : "1";
+    String s =  h.query(req("q", "id:3", "qt", "/export", "fl", "int_is_t", "sort", "intdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":1, \"docs\":[{\"int_is_t\":["+expected+"]}]}}");
+    expected = h.getCore().getLatestSchema().getField("int_is_p").getType().isPointField()
+      ? "1,1,1,1" : "1";
+    s =  h.query(req("q", "id:8", "qt", "/export", "fl", "int_is_p", "sort", "intdv asc"));
+    assertJsonEquals(s, "{\"responseHeader\": {\"status\": 0}, \"response\":{\"numFound\":1, \"docs\":[{\"int_is_p\":[1,1,1,1]}]}}");
+  }
+  
+  /**
+   * This test doesn't validate the correctness of results, it just compares the response of the same request
+   * when asking for Trie fields vs Point fields. Can be removed once Trie fields are no longer supported
+   */
+  @Test
+  @SuppressForbidden(reason="using new Date(time) to create random dates")
+  public void testRandomNumerics() throws Exception {
+    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ROOT);
+    assertU(delQ("*:*"));
+    assertU(commit());
+    List<String> trieFields = new ArrayList<String>();
+    List<String> pointFields = new ArrayList<String>();
+    for (String mv:new String[]{"s", ""}) {
+      for (String indexed:new String[]{"_ni", ""}) {
+        for (String type:new String[]{"i", "l", "f", "d", "dt"}) {
+          String field = "number_" + type + mv + indexed;
+          SchemaField sf = h.getCore().getLatestSchema().getField(field + "_t");
+          assertTrue(sf.hasDocValues());
+          assertTrue(sf.getType().getNumberType() != null);
+          
+          sf = h.getCore().getLatestSchema().getField(field + "_p");
+          assertTrue(sf.hasDocValues());
+          assertTrue(sf.getType().getNumberType() != null);
+          assertTrue(sf.getType().isPointField());
+          
+          trieFields.add(field + "_t");
+          pointFields.add(field + "_p");
+        }
+      }
+    }
+    for (int i = 0; i < atLeast(100); i++) {
+      if (random().nextInt(20) == 0) {
+        //have some empty docs
+        assertU(adoc("id", String.valueOf(i)));
+        continue;
+      }
+
+      if (random().nextInt(20) == 0 && i > 0) {
+        //delete some docs
+        assertU(delI(String.valueOf(i - 1)));
+      }
+      
+      SolrInputDocument doc = new SolrInputDocument();
+      doc.addField("id", String.valueOf(i));
+      addInt(doc, random().nextInt(), false);
+      addLong(doc, random().nextLong(), false);
+      addFloat(doc, random().nextFloat() * 3000 * (random().nextBoolean()?1:-1), false);
+      addDouble(doc, random().nextDouble() * 3000 * (random().nextBoolean()?1:-1), false);
+      addDate(doc, dateFormat.format(new Date()), false);
+
+      // MV need to be unique in order to be the same in Trie vs Points
+      Set<Integer> ints = new HashSet<>();
+      Set<Long> longs = new HashSet<>();
+      Set<Float> floats = new HashSet<>();
+      Set<Double> doubles = new HashSet<>();
+      Set<String> dates = new HashSet<>();
+      for (int j=0; j < random().nextInt(20); j++) {
+        ints.add(random().nextInt());
+        longs.add(random().nextLong());
+        floats.add(random().nextFloat() * 3000 * (random().nextBoolean()?1:-1));
+        doubles.add(random().nextDouble() * 3000 * (random().nextBoolean()?1:-1));
+        dates.add(dateFormat.format(new Date(System.currentTimeMillis() + random().nextInt())));
+      }
+      ints.stream().forEach((val)->addInt(doc, val, true));
+      longs.stream().forEach((val)->addLong(doc, val, true));
+      floats.stream().forEach((val)->addFloat(doc, val, true));
+      doubles.stream().forEach((val)->addDouble(doc, val, true));
+      dates.stream().forEach((val)->addDate(doc, val, true));
+      
+      assertU(adoc(doc));
+      if (random().nextInt(20) == 0) {
+        assertU(commit());
+      }
+    }
+    assertU(commit());
+    doTestQuery("id:1", trieFields, pointFields);
+    doTestQuery("*:*", trieFields, pointFields);
+    doTestQuery("id:[0 TO 2]", trieFields, pointFields);// "id" field is really a string, this is not a numeric range query
+    doTestQuery("id:[0 TO 9]", trieFields, pointFields);
+    doTestQuery("id:DOES_NOT_EXIST", trieFields, pointFields);
+  }
+
+  @Test
+  public void testMultipleSorts() throws Exception {
+    assertU(delQ("*:*"));
+    assertU(commit());
+
+    int numDocs = 1000;
+
+    //10 unique values
+    String[] str_vals = new String[10];
+    for (int i=0; i<str_vals.length; i++) {
+      str_vals[i] = TestUtil.randomSimpleString(random(), 10);
+    }
+
+    float[] float_vals = new float[10];
+    float_vals[0] = 0.0f;
+    float_vals[1] = +0.0f;
+    float_vals[2] = -0.0f;
+    float_vals[3] = +0.00001f;
+    float_vals[4] = +0.000011f;
+    float_vals[5] = Float.MAX_VALUE;
+    float_vals[6] = Float.MIN_VALUE;
+    float_vals[7] = 1/3f; //0.33333334
+    float_vals[8] = 0.33333333f;
+    float_vals[9] = random().nextFloat();
+
+    for (int i = 0; i < numDocs; i++) {
+      int number = TestUtil.nextInt(random(), 0, 9);
+      assertU(adoc("id", String.valueOf(i),
+          "floatdv", String.valueOf(number),
+          "intdv", String.valueOf(number),
+          "stringdv", String.valueOf(str_vals[number]),
+          "longdv", String.valueOf(number),
+          "doubledv", String.valueOf(number),
+          "datedv", randomSkewedDate(),
+          "booleandv", String.valueOf(random().nextBoolean()),
+          "field1_s_dv", String.valueOf(str_vals[number]),
+          "field2_i_p", String.valueOf(number),
+          "field3_l_p", String.valueOf(number)));
+      if (numDocs % 3000 ==0) {
+        assertU(commit());
+      }
+    }
+    assertU(commit());
+
+    validateSort(numDocs);
+  }
+
+  private void validateSort(int numDocs) throws Exception {
+    // 10 fields
+    List<String> fieldNames = new ArrayList<>(Arrays.asList("floatdv", "intdv", "stringdv", "longdv", "doubledv",
+        "datedv", "booleandv", "field1_s_dv", "field2_i_p", "field3_l_p"));
+
+    SortFields[] fieldSorts = new SortFields[TestUtil.nextInt(random(), 1, fieldNames.size())];
+    for (int i = 0; i < fieldSorts.length; i++) {
+      fieldSorts[i] = new SortFields(fieldNames.get(TestUtil.nextInt(random(), 0, fieldNames.size() - 1)));
+      fieldNames.remove(fieldSorts[i].getField());
+    }
+    String[] fieldWithOrderStrs = new String[fieldSorts.length];
+    String[] fieldStrs = new String[fieldSorts.length];
+    for (int i = 0; i < fieldSorts.length; i++) {
+      fieldWithOrderStrs[i] = fieldSorts[i].getFieldWithOrder();
+      fieldStrs[i] = fieldSorts[i].getField();
+    }
+
+    String sortStr = String.join(",", fieldWithOrderStrs); // sort : field1 asc, field2 desc
+    String fieldsStr = String.join(",", fieldStrs); // fl :  field1, field2
+
+    String resp = h.query(req("q", "*:*", "qt", "/export", "fl", "id," + fieldsStr, "sort", sortStr));
+
+    //We cannot compare /select vs /export as for docs with the same values ( ties ) the ordering is different
+    SolrQueryRequest req = null;
+    try {
+      req = req("q", "*:*", "qt", "/select", "fl", "id," + fieldsStr, "sort", sortStr, "rows", Integer.toString(numDocs));
+
+      SolrQueryResponse selectRsp = h.queryAndResponse("", req);
+      DocList selectDocList = ((ResultContext)selectRsp.getResponse()).getDocList();
+      assert selectDocList.size() == numDocs;
+      DocIterator selectDocListIter = selectDocList.iterator();
+
+      ObjectMapper mapper = new ObjectMapper();
+      HashMap respMap = mapper.readValue(resp, HashMap.class);
+      List docs = (ArrayList) ((HashMap) respMap.get("response")).get("docs");
+      assert docs.size() == numDocs;
+
+      for (int i = 0; i < docs.size() - 1; i++) { // docs..
+        assertEquals("Position:" + i + " has different id value" , String.valueOf(selectDocListIter.nextDoc()), String.valueOf(((HashMap) docs.get(i)).get("id")));
+
+        for (int j = 0; j < fieldSorts.length; j++) { // fields ..
+          String field = fieldSorts[j].getField();
+          String sort = fieldSorts[j].getSort();
+          String fieldVal1 = String.valueOf(((HashMap) docs.get(i)).get(field)); // 1st doc
+          String fieldVal2 = String.valueOf(((HashMap) docs.get(i + 1)).get(field)); // 2nd obj
+          if (fieldVal1.equals(fieldVal2)) {
+            continue;
+          } else {
+            if (sort.equals("asc")) {
+              if (field.equals("stringdv") || field.equals("field1_s_dv")|| field.equals("datedv") || field.equals("booleandv")) { // use string comparator
+                assertTrue(fieldVal1.compareTo(fieldVal2) < 0);
+              } else if (field.equals("doubledv")){
+                assertTrue(Double.compare(Double.valueOf(fieldVal1), Double.valueOf(fieldVal2)) <= 0);
+              } else if(field.equals("floatdv")) {
+                assertTrue(Float.compare(Float.valueOf(fieldVal1), Float.valueOf(fieldVal2)) <= 0);
+              } else if(field.equals("intdv") || "field2_i_p".equals(field)) {
+                assertTrue(Integer.compare(Integer.valueOf(fieldVal1), Integer.valueOf(fieldVal2)) <= 0);
+              } else if(field.equals("longdv") || field.equals("field3_l_p")) {
+                assertTrue(Long.compare(Integer.valueOf(fieldVal1), Long.valueOf(fieldVal2)) <= 0);
+              }
+            } else {
+              if (field.equals("stringdv") || field.equals("field1_s_dv")|| field.equals("datedv") || field.equals("booleandv")) { // use string comparator
+                assertTrue(fieldVal1.compareTo(fieldVal2) > 0);
+              } else if (field.equals("doubledv")){
+                assertTrue(Double.compare(Double.valueOf(fieldVal1), Double.valueOf(fieldVal2)) >= 0);
+              } else if(field.equals("floatdv")) {
+                assertTrue(Float.compare(Float.valueOf(fieldVal1), Float.valueOf(fieldVal2)) >= 0);
+              } else if(field.equals("intdv") || "field2_i_p".equals(field)) {
+                assertTrue(Integer.compare(Integer.valueOf(fieldVal1), Integer.valueOf(fieldVal2)) >= 0);
+              } else if(field.equals("longdv") || field.equals("field3_l_p")) {
+                assertTrue(Long.compare(Integer.valueOf(fieldVal1), Long.valueOf(fieldVal2)) >= 0);
+              }
+            }
+            break;
+          }
+        }
+      }
+    } finally {
+      if (req != null) {
+        req.close();
+      }
+    }
+  }
+
+  private class SortFields {
+    String fieldName;
+    String sortOrder;
+    String[] orders = {"asc", "desc"};
+
+    SortFields(String fn) {
+      this.fieldName = fn;
+      this.sortOrder = orders[random().nextInt(2)];
+    }
+
+    public String getFieldWithOrder() {
+      return this.fieldName + " " + this.sortOrder;
+    }
+
+    public String getField() {
+      return this.fieldName;
+    }
+
+    public String getSort() {
+      return this.sortOrder;
+    }
+  }
+
+  private void doTestQuery(String query, List<String> trieFields, List<String> pointFields) throws Exception {
+    String trieFieldsFl = String.join(",", trieFields);
+    String pointFieldsFl = String.join(",", pointFields);
+    String sort = pickRandom((String)pickRandom(trieFields.toArray()), (String)pickRandom(pointFields.toArray())).replace("s_", "_") + pickRandom(" asc", " desc");
+    String resultPoints =  h.query(req("q", query, "qt", "/export", "fl", pointFieldsFl, "sort", sort));
+    String resultTries =  h.query(req("q", query, "qt", "/export", "fl", trieFieldsFl, "sort", sort));
+    assertJsonEquals(resultPoints.replaceAll("_p", ""), resultTries.replaceAll("_t", ""));
+  }
+
+  private void addFloat(SolrInputDocument doc, float value, boolean mv) {
+    addField(doc, "f", String.valueOf(value), mv);
+  }
+
+  private void addDouble(SolrInputDocument doc, double value, boolean mv) {
+    addField(doc, "d", String.valueOf(value), mv);
+  }
+
+  private void addLong(SolrInputDocument doc, long value, boolean mv) {
+    addField(doc, "l", String.valueOf(value), mv);
+  }
+
+  private void addInt(SolrInputDocument doc, int value, boolean mv) {
+    addField(doc, "i", String.valueOf(value), mv);
+  }
+  
+  private void addDate(SolrInputDocument doc, String value, boolean mv) {
+    addField(doc, "dt", value, mv);
+  }
+  
+  private void addField(SolrInputDocument doc, String type, String value, boolean mv) {
+    doc.addField("number_" + type + (mv?"s":"") + "_t", value);
+    doc.addField("number_" + type + (mv?"s":"") + "_p", value);
+    doc.addField("number_" + type + (mv?"s":"") + "_ni_t", value);
+    doc.addField("number_" + type + (mv?"s":"") + "_ni_p", value);
+  }
+
+}


Mime
View raw message