mirror of https://github.com/apache/lucene.git
SOLR-1297: improvements to sort param parsing code so more fields with exentric names (that were supported for sorting in older versions of solr) will be checked for after attemptint to parse as a function
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1045253 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
625f60388d
commit
26dd4f34cd
|
@ -67,6 +67,7 @@ public class QueryParsing {
|
||||||
public static final String LOCALPARAM_START = "{!";
|
public static final String LOCALPARAM_START = "{!";
|
||||||
public static final char LOCALPARAM_END = '}';
|
public static final char LOCALPARAM_END = '}';
|
||||||
public static final String DOCID = "_docid_";
|
public static final String DOCID = "_docid_";
|
||||||
|
public static final String SCORE = "score";
|
||||||
|
|
||||||
// true if the value was specified by the "v" param (i.e. v=myval, or v=$param)
|
// true if the value was specified by the "v" param (i.e. v=myval, or v=$param)
|
||||||
public static final String VAL_EXPLICIT = "__VAL_EXPLICIT__";
|
public static final String VAL_EXPLICIT = "__VAL_EXPLICIT__";
|
||||||
|
@ -300,10 +301,11 @@ public class QueryParsing {
|
||||||
while (sp.pos < sp.end) {
|
while (sp.pos < sp.end) {
|
||||||
sp.eatws();
|
sp.eatws();
|
||||||
|
|
||||||
int start = sp.pos;
|
final int start = sp.pos;
|
||||||
|
|
||||||
|
// short circuit test for a really simple field name
|
||||||
String field = sp.getId(null);
|
String field = sp.getId(null);
|
||||||
ValueSource vs = null;
|
ParseException qParserException = null;
|
||||||
|
|
||||||
if (field == null || sp.ch() != ' ') {
|
if (field == null || sp.ch() != ' ') {
|
||||||
// let's try it as a function instead
|
// let's try it as a function instead
|
||||||
|
@ -311,89 +313,100 @@ public class QueryParsing {
|
||||||
|
|
||||||
QParser parser = QParser.getParser(funcStr, FunctionQParserPlugin.NAME, req);
|
QParser parser = QParser.getParser(funcStr, FunctionQParserPlugin.NAME, req);
|
||||||
Query q = null;
|
Query q = null;
|
||||||
if (parser instanceof FunctionQParser) {
|
try {
|
||||||
FunctionQParser fparser = (FunctionQParser)parser;
|
if (parser instanceof FunctionQParser) {
|
||||||
fparser.setParseMultipleSources(false);
|
FunctionQParser fparser = (FunctionQParser)parser;
|
||||||
fparser.setParseToEnd(false);
|
fparser.setParseMultipleSources(false);
|
||||||
|
fparser.setParseToEnd(false);
|
||||||
q = fparser.getQuery();
|
|
||||||
|
q = fparser.getQuery();
|
||||||
if (fparser.localParams != null) {
|
|
||||||
if (fparser.valFollowedParams) {
|
if (fparser.localParams != null) {
|
||||||
|
if (fparser.valFollowedParams) {
|
||||||
|
// need to find the end of the function query via the string parser
|
||||||
|
int leftOver = fparser.sp.end - fparser.sp.pos;
|
||||||
|
sp.pos = sp.end - leftOver; // reset our parser to the same amount of leftover
|
||||||
|
} else {
|
||||||
|
// the value was via the "v" param in localParams, so we need to find
|
||||||
|
// the end of the local params themselves to pick up where we left off
|
||||||
|
sp.pos = start + fparser.localParamsEnd;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// need to find the end of the function query via the string parser
|
// need to find the end of the function query via the string parser
|
||||||
int leftOver = fparser.sp.end - fparser.sp.pos;
|
int leftOver = fparser.sp.end - fparser.sp.pos;
|
||||||
sp.pos = sp.end - leftOver; // reset our parser to the same amount of leftover
|
sp.pos = sp.end - leftOver; // reset our parser to the same amount of leftover
|
||||||
} else {
|
|
||||||
// the value was via the "v" param in localParams, so we need to find
|
|
||||||
// the end of the local params themselves to pick up where we left off
|
|
||||||
sp.pos = start + fparser.localParamsEnd;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// need to find the end of the function query via the string parser
|
// A QParser that's not for function queries.
|
||||||
int leftOver = fparser.sp.end - fparser.sp.pos;
|
// It must have been specified via local params.
|
||||||
sp.pos = sp.end - leftOver; // reset our parser to the same amount of leftover
|
q = parser.getQuery();
|
||||||
|
|
||||||
|
assert parser.getLocalParams() != null;
|
||||||
|
sp.pos = start + parser.localParamsEnd;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// A QParser that's not for function queries.
|
|
||||||
// It must have been specified via local params.
|
|
||||||
q = parser.getQuery();
|
|
||||||
|
|
||||||
assert parser.getLocalParams() != null;
|
Boolean top = sp.getSortDirection();
|
||||||
sp.pos = start + parser.localParamsEnd;
|
if (null != top) {
|
||||||
}
|
// we have a Query and a valid direction
|
||||||
|
if (q instanceof FunctionQuery) {
|
||||||
// OK, now we have our query.
|
lst.add(((FunctionQuery)q).getValueSource().getSortField(top));
|
||||||
if (q instanceof FunctionQuery) {
|
} else {
|
||||||
vs = ((FunctionQuery)q).getValueSource();
|
lst.add((new QueryValueSource(q, 0.0f)).getSortField(top));
|
||||||
} else {
|
}
|
||||||
vs = new QueryValueSource(q, 0.0f);
|
continue;
|
||||||
|
}
|
||||||
|
} catch (ParseException e) {
|
||||||
|
// hang onto this in case the string isn't a full field name either
|
||||||
|
qParserException = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we have our field or value source, so find the sort order
|
// if we made it here, we either have a "simple" field name,
|
||||||
String order = sp.getId("Expected sort order asc/desc");
|
// or there was a problem parsing the string as a complex func/quer
|
||||||
boolean top;
|
|
||||||
if ("desc".equals(order) || "top".equals(order)) {
|
if (field == null) {
|
||||||
top = true;
|
// try again, simple rules for a field name with no whitespace
|
||||||
} else if ("asc".equals(order) || "bottom".equals(order)) {
|
sp.pos = start;
|
||||||
top = false;
|
field = sp.getSimpleString();
|
||||||
|
}
|
||||||
|
Boolean top = sp.getSortDirection();
|
||||||
|
if (null == top) {
|
||||||
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
||||||
|
"Can't determine Sort Order: " + sp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SCORE.equals(field)) {
|
||||||
|
if (top) {
|
||||||
|
lst.add(SortField.FIELD_SCORE);
|
||||||
|
} else {
|
||||||
|
lst.add(new SortField(null, SortField.SCORE, true));
|
||||||
|
}
|
||||||
|
} else if (DOCID.equals(field)) {
|
||||||
|
lst.add(new SortField(null, SortField.DOC, top));
|
||||||
} else {
|
} else {
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown sort order: " + order);
|
// try to find the field
|
||||||
}
|
SchemaField sf = req.getSchema().getFieldOrNull(field);
|
||||||
|
if (null == sf) {
|
||||||
if (vs == null) {
|
if (null != qParserException) {
|
||||||
//we got the order, now deal with the sort
|
throw new SolrException
|
||||||
if ("score".equals(field)) {
|
(SolrException.ErrorCode.BAD_REQUEST,
|
||||||
if (top) {
|
"sort param could not be parsed as a query, and is not a "+
|
||||||
lst.add(SortField.FIELD_SCORE);
|
"field that exists in the index: " + field,
|
||||||
} else {
|
qParserException);
|
||||||
lst.add(new SortField(null, SortField.SCORE, true));
|
|
||||||
}
|
}
|
||||||
} else if (DOCID.equals(field)) {
|
throw new SolrException
|
||||||
lst.add(new SortField(null, SortField.DOC, top));
|
(SolrException.ErrorCode.BAD_REQUEST,
|
||||||
} else {
|
"sort param fiedl can't be found: " + field);
|
||||||
//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 sf = req.getSchema().getField(field);
|
|
||||||
|
|
||||||
// TODO: remove this - it should be up to the FieldType
|
|
||||||
if (!sf.indexed()) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "can not sort on unindexed field: " + field);
|
|
||||||
}
|
|
||||||
|
|
||||||
lst.add(sf.getType().getSortField(sf, top));
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
lst.add(vs.getSortField(top));
|
// TODO: remove this - it should be up to the FieldType
|
||||||
|
if (!sf.indexed()) {
|
||||||
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
||||||
|
"can not sort on unindexed field: "
|
||||||
|
+ field);
|
||||||
|
}
|
||||||
|
lst.add(sf.getType().getSortField(sf, top));
|
||||||
}
|
}
|
||||||
|
|
||||||
sp.eatws();
|
|
||||||
if (sp.pos < sp.end) {
|
|
||||||
sp.expect(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
|
@ -767,6 +780,56 @@ public class QueryParsing {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips leading whitespace and returns whatever sequence of non
|
||||||
|
* whitespace it can find (or hte empty string)
|
||||||
|
*/
|
||||||
|
String getSimpleString() {
|
||||||
|
eatws();
|
||||||
|
int startPos = pos;
|
||||||
|
char ch;
|
||||||
|
while (pos < end) {
|
||||||
|
ch = val.charAt(pos);
|
||||||
|
if (Character.isWhitespace(ch)) break;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
return val.substring(startPos, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort direction or null if current position does not inidcate a
|
||||||
|
* sort direction. (True is desc, False is asc).
|
||||||
|
* Position is advanced to after the comma (or end) when result is non null
|
||||||
|
*/
|
||||||
|
Boolean getSortDirection() throws ParseException {
|
||||||
|
final int startPos = pos;
|
||||||
|
final String order = getId(null);
|
||||||
|
|
||||||
|
Boolean top = null;
|
||||||
|
|
||||||
|
if (null != order) {
|
||||||
|
if ("desc".equals(order) || "top".equals(order)) {
|
||||||
|
top = true;
|
||||||
|
} else if ("asc".equals(order) || "bottom".equals(order)) {
|
||||||
|
top = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's not a legal direction if more stuff comes after it
|
||||||
|
eatws();
|
||||||
|
final char c = ch();
|
||||||
|
if (0 == c) {
|
||||||
|
// :NOOP
|
||||||
|
} else if (',' == c) {
|
||||||
|
pos++;
|
||||||
|
} else {
|
||||||
|
top = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null == top) pos = startPos; // no direction, reset
|
||||||
|
return top;
|
||||||
|
}
|
||||||
|
|
||||||
// return null if not a string
|
// return null if not a string
|
||||||
String getQuotedString() throws ParseException {
|
String getQuotedString() throws ParseException {
|
||||||
eatws();
|
eatws();
|
||||||
|
|
|
@ -365,41 +365,48 @@ public class TestFunctionQuery extends SolrTestCaseJ4 {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSortByFunc() throws Exception {
|
public void testSortByFunc() throws Exception {
|
||||||
assertU(adoc("id", "1", "x_i", "100"));
|
assertU(adoc("id", "1", "const_s", "xx", "x_i", "100", "1_s", "a"));
|
||||||
assertU(adoc("id", "2", "x_i", "300"));
|
assertU(adoc("id", "2", "const_s", "xx", "x_i", "300", "1_s", "c"));
|
||||||
assertU(adoc("id", "3", "x_i", "200"));
|
assertU(adoc("id", "3", "const_s", "xx", "x_i", "200", "1_s", "b"));
|
||||||
assertU(commit());
|
assertU(commit());
|
||||||
|
|
||||||
String desc = "/response/docs==[{'x_i':300},{'x_i':200},{'x_i':100}]";
|
String desc = "/response/docs==[{'x_i':300},{'x_i':200},{'x_i':100}]";
|
||||||
String asc = "/response/docs==[{'x_i':100},{'x_i':200},{'x_i':300}]";
|
String asc = "/response/docs==[{'x_i':100},{'x_i':200},{'x_i':300}]";
|
||||||
|
|
||||||
|
String threeonetwo = "/response/docs==[{'x_i':200},{'x_i':100},{'x_i':300}]";
|
||||||
|
|
||||||
String q = "id:[1 TO 3]";
|
String q = "id:[1 TO 3]";
|
||||||
assertJQ(req("q",q, "fl","x_i", "sort","add(x_i,x_i) desc")
|
assertJQ(req("q",q, "fl","x_i", "sort","add(x_i,x_i) desc")
|
||||||
,desc
|
,desc
|
||||||
);
|
);
|
||||||
|
|
||||||
// param sub of entire function
|
// param sub of entire function
|
||||||
assertJQ(req("q",q, "fl","x_i", "sort", "$x asc", "x","add(x_i,x_i)")
|
assertJQ(req("q",q, "fl","x_i", "sort", "const_s asc, $x asc", "x","add(x_i,x_i)")
|
||||||
,asc
|
,asc
|
||||||
);
|
);
|
||||||
|
|
||||||
// multiple functions
|
// multiple functions
|
||||||
assertJQ(req("q",q, "fl","x_i", "sort", "$x asc, $y desc", "x", "5", "y","add(x_i,x_i)")
|
assertJQ(req("q",q, "fl","x_i", "sort", "$x asc, const_s asc, $y desc", "x", "5", "y","add(x_i,x_i)")
|
||||||
,desc
|
,desc
|
||||||
);
|
);
|
||||||
|
|
||||||
// multiple functions inline
|
// multiple functions inline
|
||||||
assertJQ(req("q",q, "fl","x_i", "sort", "add( 10 , 10 ) asc, add(x_i , $const) desc", "const","50")
|
assertJQ(req("q",q, "fl","x_i", "sort", "add( 10 , 10 ) asc, const_s asc, add(x_i , $const) desc", "const","50")
|
||||||
,desc
|
,desc
|
||||||
);
|
);
|
||||||
|
|
||||||
// test function w/ local params + func inline
|
// test function w/ local params + func inline
|
||||||
assertJQ(req("q",q, "fl","x_i", "sort", "{!key=foo}add(x_i,x_i) desc")
|
assertJQ(req("q",q, "fl","x_i",
|
||||||
,desc
|
"sort", "const_s asc, {!key=foo}add(x_i,x_i) desc")
|
||||||
|
,desc
|
||||||
|
);
|
||||||
|
assertJQ(req("q",q, "fl","x_i",
|
||||||
|
"sort", "{!key=foo}add(x_i,x_i) desc, const_s asc")
|
||||||
|
,desc
|
||||||
);
|
);
|
||||||
|
|
||||||
// test multiple functions w/ local params + func inline
|
// test multiple functions w/ local params + func inline
|
||||||
assertJQ(req("q",q, "fl","x_i", "sort", "{!key=bar}add(10,20) asc, {!key=foo}add(x_i,x_i) desc")
|
assertJQ(req("q",q, "fl","x_i", "sort", "{!key=bar}add(10,20) asc, const_s asc, {!key=foo}add(x_i,x_i) desc")
|
||||||
,desc
|
,desc
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -407,6 +414,31 @@ public class TestFunctionQuery extends SolrTestCaseJ4 {
|
||||||
assertJQ(req("q",q, "fl","x_i", "sort", "{!key=bar v=$s1} asc, {!key=foo v=$s2} desc", "s1","add(3,4)", "s2","add(x_i,5)")
|
assertJQ(req("q",q, "fl","x_i", "sort", "{!key=bar v=$s1} asc, {!key=foo v=$s2} desc", "s1","add(3,4)", "s2","add(x_i,5)")
|
||||||
,desc
|
,desc
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// no space between inlined localparams and sort order
|
||||||
|
assertJQ(req("q",q, "fl","x_i", "sort", "{!key=bar v=$s1}asc,const_s asc,{!key=foo v=$s2}desc", "s1","add(3,4)", "s2","add(x_i,5)")
|
||||||
|
,desc
|
||||||
|
);
|
||||||
|
|
||||||
|
// field name that isn't a legal java Identifier
|
||||||
|
// and starts with a number to trick function parser
|
||||||
|
assertJQ(req("q",q, "fl","x_i", "sort", "1_s asc")
|
||||||
|
,asc
|
||||||
|
);
|
||||||
|
|
||||||
|
// really ugly field name that isn't a java Id, and can't be
|
||||||
|
// parsed as a func, but sorted fine in Solr 1.4
|
||||||
|
assertJQ(req("q",q, "fl","x_i",
|
||||||
|
"sort", "[]_s asc, {!key=foo}add(x_i,x_i) desc")
|
||||||
|
,desc
|
||||||
|
);
|
||||||
|
// use localparms to sort by a lucene query, then a function
|
||||||
|
assertJQ(req("q",q, "fl","x_i",
|
||||||
|
"sort", "{!lucene v='id:3'}desc, {!key=foo}add(x_i,x_i) asc")
|
||||||
|
,threeonetwo
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -478,4 +510,4 @@ public class TestFunctionQuery extends SolrTestCaseJ4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue