diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 2d5908bb861..e3b2bf7fb2e 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -268,6 +268,10 @@ New Features * SOLR-792: Adding PivotFacetComponent for Hierarchical faceting (erik, Jeremy Hinegardner, Thibaut Lassalle, ryan) +* SOLR-2128: Full parameter substitution for function queries. + Example: q=add($v1,$v2)&v1=mul(popularity,5)&v2=20.0 + (yonik) + Optimizations ---------------------- diff --git a/solr/src/java/org/apache/solr/search/FunctionQParser.java b/solr/src/java/org/apache/solr/search/FunctionQParser.java index ac664cd27e6..0379e1718ea 100755 --- a/solr/src/java/org/apache/solr/search/FunctionQParser.java +++ b/solr/src/java/org/apache/solr/search/FunctionQParser.java @@ -70,8 +70,8 @@ public class FunctionQParser extends QParser { * @throws ParseException */ public String parseId() throws ParseException { - String value = sp.getId(); - consumeArgumentDelimiter(); + String value = parseArg(); + if (argWasQuoted) throw new ParseException("Expected identifier instead of quoted string:" + value); return value; } @@ -82,8 +82,9 @@ public class FunctionQParser extends QParser { * @throws ParseException */ public Float parseFloat() throws ParseException { - float value = sp.getFloat(); - consumeArgumentDelimiter(); + String str = parseArg(); + if (argWasQuoted()) throw new ParseException("Expected float instead of quoted string:" + str); + float value = Float.parseFloat(str); return value; } @@ -93,8 +94,9 @@ public class FunctionQParser extends QParser { * @throws ParseException */ public double parseDouble() throws ParseException { - double value = sp.getDouble(); - consumeArgumentDelimiter(); + String str = parseArg(); + if (argWasQuoted()) throw new ParseException("Expected double instead of quoted string:" + str); + double value = Double.parseDouble(str); return value; } @@ -104,12 +106,21 @@ public class FunctionQParser extends QParser { * @throws ParseException */ public int parseInt() throws ParseException { - int value = sp.getInt(); - consumeArgumentDelimiter(); + String str = parseArg(); + if (argWasQuoted()) throw new ParseException("Expected double instead of quoted string:" + str); + int value = Integer.parseInt(str); return value; } + + private boolean argWasQuoted; + public boolean argWasQuoted() { + return argWasQuoted; + } + public String parseArg() throws ParseException { + argWasQuoted = false; + sp.eatws(); char ch = sp.peek(); String val = null; @@ -123,6 +134,7 @@ public class FunctionQParser extends QParser { case '\'': case '"': val = sp.getQuotedString(); + argWasQuoted = true; break; default: // read unquoted literal ended by whitespace ',' or ')' @@ -232,11 +244,65 @@ public class FunctionQParser extends QParser { int ch = sp.peek(); if (ch>='0' && ch<='9' || ch=='.' || ch=='+' || ch=='-') { - valueSource = new ConstValueSource(sp.getFloat()); + Number num = sp.getNumber(); + if (num instanceof Long) { + valueSource = new LongConstValueSource(num.longValue()); + } else if (num instanceof Double) { + valueSource = new DoubleConstValueSource(num.doubleValue()); + } else { + // shouldn't happen + valueSource = new ConstValueSource(num.floatValue()); + } } else if (ch == '"' || ch == '\''){ valueSource = new LiteralValueSource(sp.getQuotedString()); - } - else { + } else if (ch == '$') { + sp.pos++; + String param = sp.getId(); + String val = getParam(param); + if (val == null) { + throw new ParseException("Missing param " + param + " while parsing function '" + sp.val + "'"); + } + + QParser subParser = subQuery(val, "func"); + Query subQuery = subParser.getQuery(); + if (subQuery instanceof FunctionQuery) { + valueSource = ((FunctionQuery) subQuery).getValueSource(); + } else { + valueSource = new QueryValueSource(subQuery, 0.0f); + } + + /*** + // dereference *simple* argument (i.e., can't currently be a function) + // In the future we could support full function dereferencing via a stack of ValueSource (or StringParser) objects + ch = val.length()==0 ? '\0' : val.charAt(0); + + if (ch>='0' && ch<='9' || ch=='.' || ch=='+' || ch=='-') { + QueryParsing.StrParser sp = new QueryParsing.StrParser(val); + Number num = sp.getNumber(); + if (num instanceof Long) { + valueSource = new LongConstValueSource(num.longValue()); + } else if (num instanceof Double) { + valueSource = new DoubleConstValueSource(num.doubleValue()); + } else { + // shouldn't happen + valueSource = new ConstValueSource(num.floatValue()); + } + } else if (ch == '"' || ch == '\'') { + QueryParsing.StrParser sp = new QueryParsing.StrParser(val); + val = sp.getQuotedString(); + valueSource = new LiteralValueSource(val); + } else { + if (val.length()==0) { + valueSource = new LiteralValueSource(val); + } else { + String id = val; + SchemaField f = req.getSchema().getField(id); + valueSource = f.getType().getValueSource(f, this); + } + } + ***/ + + } else { String id = sp.getId(); if (sp.opt("(")) { @@ -252,6 +318,7 @@ public class FunctionQParser extends QParser { SchemaField f = req.getSchema().getField(id); valueSource = f.getType().getValueSource(f, this); } + } if (doConsumeDelimiter) diff --git a/solr/src/java/org/apache/solr/search/QueryParsing.java b/solr/src/java/org/apache/solr/search/QueryParsing.java index a71a0038507..973e4ca1df8 100644 --- a/solr/src/java/org/apache/solr/search/QueryParsing.java +++ b/solr/src/java/org/apache/solr/search/QueryParsing.java @@ -694,6 +694,27 @@ public class QueryParsing { return Float.parseFloat(new String(arr, 0, i)); } + Number getNumber() throws ParseException { + eatws(); + int start = pos; + boolean flt = false; + + while (pos < end) { + char ch = val.charAt(pos); + if ((ch >= '0' && ch <= '9') || ch == '+' || ch == '-') { + pos++; + } else if (ch == '.' || ch =='e' || ch=='E') { + flt = true; + pos++; + } else { + break; + } + } + + String v = val.substring(start,pos); + return flt ? Double.parseDouble(v) : Long.parseLong(v); + } + double getDouble() throws ParseException { eatws(); char[] arr = new char[end - pos]; diff --git a/solr/src/test/org/apache/solr/search/FunctionQParserTest.java b/solr/src/test/org/apache/solr/search/FunctionQParserTest.java index a8b588837a4..b7640b5a0ee 100644 --- a/solr/src/test/org/apache/solr/search/FunctionQParserTest.java +++ b/solr/src/test/org/apache/solr/search/FunctionQParserTest.java @@ -42,14 +42,6 @@ public class FunctionQParserTest extends AbstractSolrTestCase { assertTrue("query is not a FunctionQuery", query instanceof FunctionQuery); fq = (FunctionQuery) query; assertTrue("ValueSource is not a LiteralValueSource", fq.getValueSource() instanceof LiteralValueSource); - - parser = new FunctionQParser("1.5", local, params, req); - query = parser.parse(); - assertTrue("query is not a FunctionQuery", query instanceof FunctionQuery); - fq = (FunctionQuery) query; - assertTrue("ValueSource is not a LiteralValueSource", fq.getValueSource() instanceof ConstValueSource); - - //TODO: Add more tests here to test the parser } } diff --git a/solr/src/test/org/apache/solr/search/function/TestFunctionQuery.java b/solr/src/test/org/apache/solr/search/function/TestFunctionQuery.java index 99f3e4232f2..624dafe157f 100755 --- a/solr/src/test/org/apache/solr/search/function/TestFunctionQuery.java +++ b/solr/src/test/org/apache/solr/search/function/TestFunctionQuery.java @@ -339,6 +339,10 @@ public class TestFunctionQuery extends SolrTestCaseJ4 { assertQ(req("fl","*,score","q", q, "qq","text:superman", "fq",fq), "//float[@name='score']>'1.0'"); + // test full param dereferencing + assertQ(req("fl","*,score","q", "{!func}add($v1,$v2)", "v1","add($v3,$v4)", "v2","1", "v3","2", "v4","5" + , "fq","id:1"), "//float[@name='score']='8.0'"); + purgeFieldCache(FieldCache.DEFAULT); // avoid FC insanity }