You are viewing a plain text version of this content. The canonical link for it is here.
Posted to solr-commits@lucene.apache.org by gs...@apache.org on 2009/12/13 00:00:27 UTC
svn commit: r889997 - in /lucene/solr/trunk: ./
src/java/org/apache/solr/search/ src/java/org/apache/solr/search/function/
src/test/org/apache/solr/search/ src/test/org/apache/solr/search/function/
Author: gsingers
Date: Sat Dec 12 23:00:27 2009
New Revision: 889997
URL: http://svn.apache.org/viewvc?rev=889997&view=rev
Log:
SOLR-1297: Added Sort By Function capability
Added:
lucene/solr/trunk/src/test/org/apache/solr/search/function/SortByFunctionTest.java (with props)
Modified:
lucene/solr/trunk/CHANGES.txt
lucene/solr/trunk/src/java/org/apache/solr/search/QueryParsing.java
lucene/solr/trunk/src/java/org/apache/solr/search/function/ValueSource.java
lucene/solr/trunk/src/test/org/apache/solr/search/QueryParsingTest.java
Modified: lucene/solr/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/CHANGES.txt?rev=889997&r1=889996&r2=889997&view=diff
==============================================================================
--- lucene/solr/trunk/CHANGES.txt (original)
+++ lucene/solr/trunk/CHANGES.txt Sat Dec 12 23:00:27 2009
@@ -58,6 +58,8 @@
* SOLR-1625: Add regexp support for TermsComponent (Uri Boness via noble)
+* SOLR-1297: Add sort by Function capability (gsingers)
+
Optimizations
----------------------
Modified: lucene/solr/trunk/src/java/org/apache/solr/search/QueryParsing.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/QueryParsing.java?rev=889997&r1=889996&r2=889997&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/QueryParsing.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/QueryParsing.java Sat Dec 12 23:00:27 2009
@@ -41,6 +41,7 @@
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.function.FunctionQuery;
+import org.apache.solr.search.function.ValueSource;
import java.io.IOException;
import java.util.ArrayList;
@@ -216,7 +217,6 @@
}
- private static Pattern sortSep = Pattern.compile(",");
/**
* Returns null if the sortSpec is the standard sort desc.
@@ -240,58 +240,145 @@
*/
public static Sort parseSort(String sortSpec, IndexSchema schema) {
if (sortSpec == null || sortSpec.length() == 0) return null;
+ char[] chars = sortSpec.toCharArray();
+ int i = 0;
+ StringBuilder buffer = new StringBuilder(sortSpec.length());
+ String sort = null;
+ String order = null;
+ int functionDepth = 0;
+ boolean score = true;
+ List<SortField> lst = new ArrayList<SortField>(5);
+ boolean needOrder = false;
+ while (i < chars.length) {
+ if (Character.isWhitespace(chars[i]) && functionDepth == 0) {
+ if (buffer.length() == 0) {
+ //do nothing
+ } else {
+ if (needOrder == false) {
+ sort = buffer.toString().trim();
+ buffer.setLength(0);
+ needOrder = true;
+ } else {
+ order = buffer.toString().trim();
+ buffer.setLength(0);
+ needOrder = false;
+ }
+ }
+ } else if (chars[i] == '(' && functionDepth == 0) {
+ buffer.append(chars[i]);
+ functionDepth++;
+ } else if (chars[i] == ')' && functionDepth > 0) {
+ buffer.append(chars[i]);
+ functionDepth--;//close up one layer
+ } else if (chars[i] == ',' && functionDepth == 0) {//can either be a separator of sort declarations, or a separator in a function
+ //we have a separator between sort declarations,
+ // We may need an order still, but then evaluate it, as we should have everything we need
+ if (needOrder == true && buffer.length() > 0){
+ order = buffer.toString().trim();
+ buffer.setLength(0);
+ needOrder = false;
+ }
+ score = processSort(schema, sort, order, lst);
+ sort = null;
+ order = null;
+ buffer.setLength(0);//get ready for the next one, if there is one
+ } else if (chars[i] == ',' && functionDepth > 0) {
+ //we are in a function
+ buffer.append(chars[i]);
+ } else {
+ //just a regular old char, add it to the buffer
+ buffer.append(chars[i]);
+ }
+ i++;
+ }
+ if (buffer.length() > 0 && needOrder){//see if we have anything left, at most it should be an order
+ order = buffer.toString().trim();
+ buffer.setLength(0);
+ needOrder = false;
+ }
- String[] parts = sortSep.split(sortSpec.trim());
- if (parts.length == 0) return null;
+ //do some sanity checks
+ if (functionDepth != 0){
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unable to parse sort spec, mismatched parentheses: " + sortSpec);
+ }
+ if (buffer.length() > 0){//there's something wrong, as everything should have been parsed by now
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unable to parse sort spec: " + sortSpec);
+ }
+ if (needOrder == false && sort != null && sort.equals("") == false && order != null && order.equals("") == false){//handle the last declaration
+ score = processSort(schema, sort, order, lst);
+ }
+ //If the normal case (by score desc) do nothing
+ if (lst.size() == 1 && score == true && lst.get(0).getReverse() == false) {
+ return null; // do normal scoring...
+ }
+ return new Sort((SortField[]) lst.toArray(new SortField[lst.size()]));
+ }
- SortField[] lst = new SortField[parts.length];
- for (int i = 0; i < parts.length; i++) {
- String part = parts[i].trim();
+ private static boolean processSort(IndexSchema schema, String sort, String order, List<SortField> lst) {
+ boolean score = false;
+ if (sort != null && order != null) {
boolean top = true;
- //determine the ordering, ascending or descending
- int idx = part.indexOf(' ');
- if (idx > 0) {
- String order = part.substring(idx + 1).trim();
- if ("desc".equals(order) || "top".equals(order)) {
- top = true;
- } else if ("asc".equals(order) || "bottom".equals(order)) {
- top = false;
- } else {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown sort order: " + order);
- }
- part = part.substring(0, idx).trim();
+ if ("desc".equals(order) || "top".equals(order)) {
+ top = true;
+ } else if ("asc".equals(order) || "bottom".equals(order)) {
+ top = false;
} else {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing sort order.");
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown sort order: " + order);
}
- //figure out the field or score
- if ("score".equals(part)) {
+ //we got the order, now deal with the sort
+ if ("score".equals(sort)) {
+ score = true;
if (top) {
- // If there is only one thing in the list, just do the regular thing...
- if (parts.length == 1) {
- return null; // do normal scoring...
- }
- lst[i] = SortField.FIELD_SCORE;
+ lst.add(SortField.FIELD_SCORE);
} else {
- lst[i] = new SortField(null, SortField.SCORE, true);
+ lst.add(new SortField(null, SortField.SCORE, true));
}
- } else if (DOCID.equals(part)) {
- lst[i] = new SortField(null, SortField.DOC, top);
+ } else if (DOCID.equals(sort)) {
+ lst.add(new SortField(null, SortField.DOC, top));
} else {
+ //See if we have a Field first, then see if it is a function, then throw an exception
// getField could throw an exception if the name isn't found
SchemaField f = null;
try {
- f = schema.getField(part);
+ f = schema.getField(sort);
}
catch (SolrException e) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "can not sort on undefined field: " + part, e);
+ //Not an error just yet
}
- if (f == null || !f.indexed()) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "can not sort on unindexed field: " + part);
+ if (f != null) {
+ if (f == null || !f.indexed()) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "can not sort on unindexed field: " + sort);
+ }
+ lst.add(f.getType().getSortField(f, top));
+ } else {
+ //See if we have a function:
+ FunctionQuery query = null;
+ try {
+ query = parseFunction(sort, schema);
+ if (query != null) {
+ ValueSource valueSource = query.getValueSource();
+ //We have a function query
+ try {
+ lst.add(valueSource.getSortField(top));
+ } catch (IOException e) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "error getting the sort for this function: " + sort, e);
+ }
+ } else {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "can not sort on undefined function: " + sort);
+ }
+ } catch (ParseException e) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "can not sort on undefined field or function: " + sort, e);
+ }
+
}
- lst[i] = f.getType().getSortField(f, top);
}
+ } else if (sort == null) {//no sort value
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+ "Must declare sort field or function");
+ } else if (order == null) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing sort order: ");
}
- return new Sort(lst);
+ return score;
}
Modified: lucene/solr/trunk/src/java/org/apache/solr/search/function/ValueSource.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/function/ValueSource.java?rev=889997&r1=889996&r2=889997&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/function/ValueSource.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/function/ValueSource.java Sat Dec 12 23:00:27 2009
@@ -18,13 +18,19 @@
package org.apache.solr.search.function;
import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.*;
-import org.apache.solr.search.function.DocValues;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.FieldComparatorSource;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.SortField;
import java.io.IOException;
import java.io.Serializable;
import java.util.IdentityHashMap;
import java.util.Map;
+import java.util.HashMap;
+import java.util.Collections;
/**
* Instantiates {@link org.apache.solr.search.function.DocValues} for a particular reader.
@@ -40,7 +46,8 @@
return getValues(null, reader);
}
- /** Gets the values for this reader and the context that was previously
+ /**
+ * Gets the values for this reader and the context that was previously
* passed to createWeight()
*/
public DocValues getValues(Map context, IndexReader reader) throws IOException {
@@ -51,23 +58,113 @@
public abstract int hashCode();
- /** description of field, used in explain() */
+ /**
+ * description of field, used in explain()
+ */
public abstract String description();
public String toString() {
return description();
}
- /** Implementations should propagate createWeight to sub-ValueSources which can optionally store
+ /**
+ * Get the SortField for this ValueSource. Uses the {@link #getValues(java.util.Map, org.apache.lucene.index.IndexReader)}
+ * to populate the SortField.
+ *
+ * @param reverse true if the order should be reversed.
+ * @return The {@link org.apache.lucene.search.SortField} for the ValueSource
+ * @throws IOException if there was a problem reading the values.
+ */
+ public SortField getSortField(boolean reverse) throws IOException {
+ //should we pass in the description for the field name?
+ //Hmm, Lucene is going to intern whatever we pass in, not sure I like that
+ //and we can't pass in null, either, as that throws an illegal arg. exception
+ return new SortField(description(), new ValueSourceComparatorSource(), reverse);
+ }
+
+
+ /**
+ * Implementations should propagate createWeight to sub-ValueSources which can optionally store
* weight info in the context. The context object will be passed to getValues()
- * where this info can be retrieved. */
+ * where this info can be retrieved.
+ */
public void createWeight(Map context, Searcher searcher) throws IOException {
}
- /** Returns a new non-threadsafe context map. */
+ /**
+ * Returns a new non-threadsafe context map.
+ */
public static Map newContext() {
return new IdentityHashMap();
}
+
+ class ValueSourceComparatorSource extends FieldComparatorSource {
+
+
+ public ValueSourceComparatorSource() {
+
+ }
+
+ public FieldComparator newComparator(String fieldname, int numHits,
+ int sortPos, boolean reversed) throws IOException {
+ return new ValueSourceComparator(numHits);
+ }
+ }
+
+ /**
+ * Implement a {@link org.apache.lucene.search.FieldComparator} that works
+ * off of the {@link org.apache.solr.search.function.DocValues} for a ValueSource
+ * instead of the normal Lucene FieldComparator that works off of a FieldCache.
+ */
+ class ValueSourceComparator extends FieldComparator {
+ private final double[] values;
+ private DocValues docVals;
+ private double bottom;
+
+ ValueSourceComparator(int numHits) {
+ values = new double[numHits];
+ }
+
+ public int compare(int slot1, int slot2) {
+ final double v1 = values[slot1];
+ final double v2 = values[slot2];
+ if (v1 > v2) {
+ return 1;
+ } else if (v1 < v2) {
+ return -1;
+ } else {
+ return 0;
+ }
+
+ }
+
+ public int compareBottom(int doc) {
+ final double v2 = docVals.doubleVal(doc);
+ if (bottom > v2) {
+ return 1;
+ } else if (bottom < v2) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ public void copy(int slot, int doc) {
+ values[slot] = docVals.doubleVal(doc);
+ }
+
+ public void setNextReader(IndexReader reader, int docBase) throws IOException {
+ docVals = getValues(Collections.emptyMap(), reader);
+ }
+
+ public void setBottom(final int bottom) {
+ this.bottom = values[bottom];
+ }
+
+ public Comparable value(int slot) {
+ return Double.valueOf(values[slot]);
+ }
+ }
}
@@ -86,7 +183,9 @@
setCheckDeletes(true);
}
- public IndexReader getReader() { return reader; }
+ public IndexReader getReader() {
+ return reader;
+ }
public void setCheckDeletes(boolean checkDeletes) {
this.checkDeletes = checkDeletes && reader.hasDeletions();
@@ -107,9 +206,9 @@
@Override
public int nextDoc() throws IOException {
- for(;;) {
+ for (; ;) {
doc++;
- if (doc >= maxDoc) return doc=NO_MORE_DOCS;
+ if (doc >= maxDoc) return doc = NO_MORE_DOCS;
if (matches(doc)) return doc;
}
}
@@ -117,7 +216,7 @@
@Override
public int advance(int target) throws IOException {
// also works fine when target==NO_MORE_DOCS
- doc = target-1;
+ doc = target - 1;
return nextDoc();
}
@@ -126,7 +225,7 @@
}
public boolean next() {
- for(;;) {
+ for (; ;) {
doc++;
if (doc >= maxDoc) return false;
if (matches(doc)) return true;
@@ -134,7 +233,7 @@
}
public boolean skipTo(int target) {
- doc = target-1;
+ doc = target - 1;
return next();
}
Modified: lucene/solr/trunk/src/test/org/apache/solr/search/QueryParsingTest.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/search/QueryParsingTest.java?rev=889997&r1=889996&r2=889997&view=diff
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/search/QueryParsingTest.java (original)
+++ lucene/solr/trunk/src/test/org/apache/solr/search/QueryParsingTest.java Sat Dec 12 23:00:27 2009
@@ -43,8 +43,14 @@
IndexSchema schema = h.getCore().getSchema();
sort = QueryParsing.parseSort("score desc", schema);
assertNull("sort", sort);//only 1 thing in the list, no Sort specified
- sort = QueryParsing.parseSort("weight desc", schema);
+
+ sort = QueryParsing.parseSort("score asc", schema);
SortField[] flds = sort.getSort();
+ assertEquals(flds[0].getType(), SortField.SCORE);
+ assertTrue(flds[0].getReverse());
+
+ sort = QueryParsing.parseSort("weight desc", schema);
+ flds = sort.getSort();
assertEquals(flds[0].getType(), SortField.FLOAT);
assertEquals(flds[0].getField(), "weight");
assertEquals(flds[0].getReverse(), true);
@@ -79,13 +85,71 @@
flds = sort.getSort();
assertEquals(flds[0].getType(), SortField.FLOAT);
assertEquals(flds[0].getField(), "weight");
- assertEquals(flds[1].getType(), SortField.LONG);
assertEquals(flds[1].getField(), "bday");
+ assertEquals(flds[1].getType(), SortField.LONG);
//handles trailing commas
sort = QueryParsing.parseSort("weight desc,", schema);
flds = sort.getSort();
assertEquals(flds[0].getType(), SortField.FLOAT);
assertEquals(flds[0].getField(), "weight");
+
+ //test functions
+ sort = QueryParsing.parseSort("pow(weight, 2) desc", schema);
+ flds = sort.getSort();
+ assertEquals(flds[0].getType(), SortField.CUSTOM);
+ //Not thrilled about the fragility of string matching here, but...
+ //the value sources get wrapped, so the out field is different than the input
+ assertEquals(flds[0].getField(), "pow(float(weight),const(2.0))");
+
+ sort = QueryParsing.parseSort("pow(weight, 2) desc", schema);
+ flds = sort.getSort();
+ assertEquals(flds[0].getType(), SortField.CUSTOM);
+ //Not thrilled about the fragility of string matching here, but...
+ //the value sources get wrapped, so the out field is different than the input
+ assertEquals(flds[0].getField(), "pow(float(weight),const(2.0))");
+
+
+ sort = QueryParsing.parseSort("pow(weight, 2) desc, weight desc, bday asc", schema);
+ flds = sort.getSort();
+ assertEquals(flds[0].getType(), SortField.CUSTOM);
+
+ //Not thrilled about the fragility of string matching here, but...
+ //the value sources get wrapped, so the out field is different than the input
+ assertEquals(flds[0].getField(), "pow(float(weight),const(2.0))");
+
+ assertEquals(flds[1].getType(), SortField.FLOAT);
+ assertEquals(flds[1].getField(), "weight");
+ assertEquals(flds[2].getField(), "bday");
+ assertEquals(flds[2].getType(), SortField.LONG);
+
+ //handles trailing commas
+ sort = QueryParsing.parseSort("weight desc,", schema);
+ flds = sort.getSort();
+ assertEquals(flds[0].getType(), SortField.FLOAT);
+ assertEquals(flds[0].getField(), "weight");
+
+ try {
+ //bad number of parens, but the function parser can handle an extra close
+ sort = QueryParsing.parseSort("pow(weight,2)) desc, bday asc", schema);
+ } catch (SolrException e) {
+ assertTrue(false);
+ }
+ //Test literals in functions
+ sort = QueryParsing.parseSort("strdist(foo_s, \"junk\", jw) desc", schema);
+ flds = sort.getSort();
+ assertEquals(flds[0].getType(), SortField.CUSTOM);
+ //the value sources get wrapped, so the out field is different than the input
+ assertEquals(flds[0].getField(), "strdist(str(foo_s),literal(junk), dist=org.apache.lucene.search.spell.JaroWinklerDistance)");
+
+ sort = QueryParsing.parseSort("", schema);
+ assertNull(sort);
+
+ }
+
+ public void testBad() throws Exception {
+ Sort sort;
+
+ IndexSchema schema = h.getCore().getSchema();
//test some bad vals
try {
sort = QueryParsing.parseSort("weight, desc", schema);
@@ -94,11 +158,38 @@
//expected
}
try {
+ sort = QueryParsing.parseSort("w", schema);
+ assertTrue(false);
+ } catch (SolrException e) {
+ //expected
+ }
+ try {
sort = QueryParsing.parseSort("weight desc, bday", schema);
assertTrue(false);
} catch (SolrException e) {
}
+ try {
+ //bad number of commas
+ sort = QueryParsing.parseSort("pow(weight,,2) desc, bday asc", schema);
+ assertTrue(false);
+ } catch (SolrException e) {
+ }
+
+ try {
+ //bad function
+ sort = QueryParsing.parseSort("pow() desc, bday asc", schema);
+ assertTrue(false);
+ } catch (SolrException e) {
+ }
+
+ try {
+ //bad number of parens
+ sort = QueryParsing.parseSort("pow((weight,2) desc, bday asc", schema);
+ assertTrue(false);
+ } catch (SolrException e) {
+ }
+
}
}
Added: lucene/solr/trunk/src/test/org/apache/solr/search/function/SortByFunctionTest.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/search/function/SortByFunctionTest.java?rev=889997&view=auto
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/search/function/SortByFunctionTest.java (added)
+++ lucene/solr/trunk/src/test/org/apache/solr/search/function/SortByFunctionTest.java Sat Dec 12 23:00:27 2009
@@ -0,0 +1,96 @@
+package org.apache.solr.search.function;
+/**
+ * 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.
+ */
+
+import org.apache.solr.util.AbstractSolrTestCase;
+
+
+/**
+ *
+ *
+ **/
+public class SortByFunctionTest extends AbstractSolrTestCase {
+ public String getSchemaFile() {
+ return "schema.xml";
+ }
+
+ public String getSolrConfigFile() {
+ return "solrconfig.xml";
+ }
+
+ public void test() throws Exception {
+ assertU(adoc("id", "1", "x_td", "0", "y_td", "2", "w_td", "25", "z_td", "5", "f_t", "ipod"));
+ assertU(adoc("id", "2", "x_td", "2", "y_td", "2", "w_td", "15", "z_td", "5", "f_t", "ipod ipod ipod ipod ipod"));
+ assertU(adoc("id", "3", "x_td", "3", "y_td", "2", "w_td", "55", "z_td", "5", "f_t", "ipod ipod ipod ipod ipod ipod ipod ipod ipod"));
+ assertU(adoc("id", "4", "x_td", "4", "y_td", "2", "w_td", "45", "z_td", "5", "f_t", "ipod ipod ipod ipod ipod ipod ipod"));
+ assertU(commit());
+
+ assertQ(req("fl", "*,score", "q", "*:*"),
+ "//*[@numFound='4']",
+ "//float[@name='score']='1.0'",
+ "//result/doc[1]/int[@name='id'][.='1']",
+ "//result/doc[2]/int[@name='id'][.='2']",
+ "//result/doc[3]/int[@name='id'][.='3']",
+ "//result/doc[4]/int[@name='id'][.='4']"
+ );
+ assertQ(req("fl", "*,score", "q", "*:*", "sort", "score desc"),
+ "//*[@numFound='4']",
+ "//float[@name='score']='1.0'",
+ "//result/doc[1]/int[@name='id'][.='1']",
+ "//result/doc[2]/int[@name='id'][.='2']",
+ "//result/doc[3]/int[@name='id'][.='3']",
+ "//result/doc[4]/int[@name='id'][.='4']"
+ );
+ assertQ(req("fl", "id,score", "q", "f_t:ipod", "sort", "score desc"),
+ "//*[@numFound='4']",
+ "//result/doc[1]/int[@name='id'][.='1']",
+ "//result/doc[2]/int[@name='id'][.='4']",
+ "//result/doc[3]/int[@name='id'][.='2']",
+ "//result/doc[4]/int[@name='id'][.='3']"
+ );
+
+
+ assertQ(req("fl", "*,score", "q", "*:*", "sort", "sum(x_td, y_td) desc"),
+ "//*[@numFound='4']",
+ "//float[@name='score']='1.0'",
+ "//result/doc[1]/int[@name='id'][.='4']",
+ "//result/doc[2]/int[@name='id'][.='3']",
+ "//result/doc[3]/int[@name='id'][.='2']",
+ "//result/doc[4]/int[@name='id'][.='1']"
+ );
+ assertQ(req("fl", "*,score", "q", "*:*", "sort", "sum(x_td, y_td) asc"),
+ "//*[@numFound='4']",
+ "//float[@name='score']='1.0'",
+ "//result/doc[1]/int[@name='id'][.='1']",
+ "//result/doc[2]/int[@name='id'][.='2']",
+ "//result/doc[3]/int[@name='id'][.='3']",
+ "//result/doc[4]/int[@name='id'][.='4']"
+ );
+ //the function is equal, w_td separates
+ assertQ(req("q", "*:*", "fl", "id", "sort", "sum(z_td, y_td) asc, w_td asc"),
+ "//*[@numFound='4']",
+ "//result/doc[1]/int[@name='id'][.='2']",
+ "//result/doc[2]/int[@name='id'][.='1']",
+ "//result/doc[3]/int[@name='id'][.='4']",
+ "//result/doc[4]/int[@name='id'][.='3']"
+ );
+ }
+}
+
+/*
+<lst name="responseHeader"><int name="status">0</int><int name="QTime">93</int></lst><result name="response" numFound="4" start="0" maxScore="1.0"><doc><float name="score">1.0</float><int name="id">4</int><int name="intDefault">42</int><arr name="multiDefault"><str>muLti-Default</str></arr><date name="timestamp">2009-12-12T12:59:46.412Z</date><arr name="x_td"><double>4.0</double></arr><arr name="y_td"><double>2.0</double></arr></doc><doc><float name="score">1.0</float><int name="id">3</int><int name="intDefault">42</int><arr name="multiDefault"><str>muLti-Default</str></arr><date name="timestamp">2009-12-12T12:59:46.409Z</date><arr name="x_td"><double>3.0</double></arr><arr name="y_td"><double>2.0</double></arr></doc><doc><float name="score">1.0</float><int name="id">2</int><int name="intDefault">42</int><arr name="multiDefault"><str>muLti-Default</str></arr><date name="timestamp">2009-12-12T12:59:46.406Z</date><arr name="x_td"><double>2.0</double></arr><arr name="y_td"><dou
ble>2.0</double></arr></doc><doc><float name="score">1.0</float><int name="id">1</int><int name="intDefault">42</int><arr name="multiDefault"><str>muLti-Default</str></arr><date name="timestamp">2009-12-12T12:59:46.361Z</date><arr name="x_td"><double>0.0</double></arr><arr name="y_td"><double>2.0</double></arr></doc></result>
+*/
\ No newline at end of file
Propchange: lucene/solr/trunk/src/test/org/apache/solr/search/function/SortByFunctionTest.java
------------------------------------------------------------------------------
svn:eol-style = native