SOLR-2444 -- refactor to support pseudo fields
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/branches/pseudo@1088812 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/solr/src/common/org/apache/solr/common/params/CommonParams.java b/solr/src/common/org/apache/solr/common/params/CommonParams.java
index 97e613f..90765e2 100755
--- a/solr/src/common/org/apache/solr/common/params/CommonParams.java
+++ b/solr/src/common/org/apache/solr/common/params/CommonParams.java
@@ -55,7 +55,11 @@
/** query and init param for field list */
public static final String FL = "fl";
- /** Pseudo fields */
+ /** Pseudo fields --
+ * To enable pseudo fields, use fl.pseudo=true
+ * then send a pseudo field in for each field that should be remapped.
+ * For example: fl.pseudo.name=foo(x,y)
+ */
public static final String PSEUDO_FL = "fl.pseudo";
/** default query field */
diff --git a/solr/src/java/org/apache/solr/response/transform/DocTransformers.java b/solr/src/java/org/apache/solr/response/transform/DocTransformers.java
index 3aef76e..dfccb56 100644
--- a/solr/src/java/org/apache/solr/response/transform/DocTransformers.java
+++ b/solr/src/java/org/apache/solr/response/transform/DocTransformers.java
@@ -48,6 +48,10 @@
str.append( "]" );
return str.toString();
}
+
+ public DocTransformer get( int idx ) {
+ return children.get( idx );
+ }
public void addTransformer( DocTransformer a ) {
children.add( a );
diff --git a/solr/src/java/org/apache/solr/response/transform/ValueAugmenterFactory.java b/solr/src/java/org/apache/solr/response/transform/ValueAugmenterFactory.java
index c2903b0..9c8c4e4 100644
--- a/solr/src/java/org/apache/solr/response/transform/ValueAugmenterFactory.java
+++ b/solr/src/java/org/apache/solr/response/transform/ValueAugmenterFactory.java
@@ -42,7 +42,7 @@
public static Object getObjectFrom( String str )
{
- int idx = str.indexOf( ':' );
+ int idx = str.indexOf( ' ' );
if( idx > 0 ) {
String type = str.substring(0,idx);
String val = str.substring(idx+1);
diff --git a/solr/src/java/org/apache/solr/search/ReturnFields.java b/solr/src/java/org/apache/solr/search/ReturnFields.java
index c571b0a..117c545 100644
--- a/solr/src/java/org/apache/solr/search/ReturnFields.java
+++ b/solr/src/java/org/apache/solr/search/ReturnFields.java
@@ -42,6 +42,7 @@
import org.apache.solr.search.function.FunctionQuery;
import org.apache.solr.search.function.QueryValueSource;
import org.apache.solr.search.function.ValueSource;
+import org.apache.solr.util.SolrPluginUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -71,10 +72,10 @@
}
public ReturnFields(SolrQueryRequest req) {
- this( req.getParams().getParams(CommonParams.FL),
- req.getParams().getParams(CommonParams.PSEUDO_FL), req );
+ this( req.getParams().getParams(CommonParams.FL), req );
}
+ // This is only used in testing?
public ReturnFields(String fl, SolrQueryRequest req) {
if( fl == null ) {
parseFieldList((String[])null, req);
@@ -94,10 +95,7 @@
}
}
- public ReturnFields(String[] fl, String[] pseudo, SolrQueryRequest req) {
- if( pseudo != null && fl != null && fl.length > 0 ) {
- parsePseudoFields(pseudo);
- }
+ public ReturnFields(String[] fl, SolrQueryRequest req) {
parseFieldList(fl, req);
}
@@ -109,10 +107,11 @@
return;
}
+ boolean checkPseudoFields = req.getParams().getBool(CommonParams.PSEUDO_FL, false);
NamedList<String> rename = new NamedList<String>();
DocTransformers augmenters = new DocTransformers();
for (String fieldList : fl) {
- add(fieldList,rename,augmenters,req);
+ addFieldParam(fieldList,checkPseudoFields,rename,augmenters,req);
}
if( rename.size() > 0 ) {
for( int i=0; i<rename.size(); i++ ) {
@@ -122,7 +121,7 @@
}
// Legacy behavior? "score" == "*,score" Distributed tests for this
- if( fields.size() == 1 && _wantsScore ) {
+ if( _wantsScore && fields.size() == 1 && augmenters.size() == 1 ) {
_wantsAllFields = true;
}
@@ -144,137 +143,127 @@
transformer = augmenters;
}
}
-
- public Map<String,String> pseudo = null;
-
- private void parsePseudoFields( String[] fields ) {
- if( fields != null ) {
- pseudo = new HashMap<String, String>();
- for( String f : fields ) {
- int idx = f.indexOf( ':' );
+
+ /**
+ * TODO, this should be a fancy parser that splits on ',' but respects quoted things
+ */
+ private void addFieldParam(String fls, boolean checkPseudoFields, NamedList<String> rename, DocTransformers augmenters, SolrQueryRequest req) {
+ if( fls != null ) {
+ String[] fields = fls.split(",");
+ for( String fl : fields ) {
+ String as = null;
+ int idx = fl.indexOf( ':' );
if( idx > 0 ) {
- String p = f.substring(0,idx);
- String r = f.substring(idx+1);
- pseudo.put(p, r);
+ as = fl.substring(0,idx).trim();
+ fl = fl.substring(idx+1).trim();
}
else {
- throw new SolrException( ErrorCode.BAD_REQUEST, "Pseudo fields must be in the form ?fl.pseudo=hello:replace" );
+ fl = fl.trim();
}
+
+ if( checkPseudoFields ) {
+ String pseudo = req.getParams().get( CommonParams.PSEUDO_FL+'.'+fl );
+ if( pseudo != null ) {
+ if( as == null ) {
+ as = fl; // use the original
+ }
+
+ // Just replace the input text
+ okFieldNames.add( fl );
+ fl = pseudo;
+ }
+ }
+
+ addField( fl, as, rename, augmenters, req );
}
}
}
-
- private void add(String fls, NamedList<String> rename, DocTransformers augmenters, SolrQueryRequest req) {
- // commas deliminate fields? is this true?
- StringTokenizer st = new StringTokenizer( fls, "," );
- while( st.hasMoreTokens() ) {
- String as = null;
- String fl = st.nextToken().trim();
- int idx = fl.lastIndexOf( " AS " );
+
+ private void addField(String fl, String display, NamedList<String> rename, DocTransformers augmenters, SolrQueryRequest req) {
+ // Maybe it is everything
+ if( "*".equals( fl ) ) {
+ if( display != null ) {
+ throw new SolrException( ErrorCode.BAD_REQUEST, "* can not use an 'AS' request" );
+ }
+ _wantsAllFields = true;
+ return;
+ }
+
+ // maybe it is a Transformer (starts and ends with [])
+ if( fl.charAt( 0 ) == '[' && fl.charAt( fl.length()-1 ) == ']' ) {
+ String name = null;
+ String args = null;
+ int idx = fl.indexOf( ' ' );
if( idx > 0 ) {
- as = fl.substring( idx+4 ).trim();
- fl = fl.substring(0,idx).trim();
- }
-
- // check if the fl is a pseudo field
- if( pseudo != null ) {
- String p = pseudo.get( fl );
- if( p != null ) {
- if( as == null ) {
- as = fl; // use the original
- }
-
- // Just replace the input text
- okFieldNames.add( fl );
- fl = p;
- }
- }
-
- // Maybe it is everything
- if( "*".equals( fl ) ) {
- if( as != null ) {
- throw new SolrException( ErrorCode.BAD_REQUEST, "* can not use an 'AS' request" );
- }
- _wantsAllFields = true;
- continue;
- }
-
- // maybe it is a Transformer (starts and ends with [])
- if( fl.charAt( 0 ) == '[' && fl.charAt( fl.length()-1 ) == ']' ) {
- String name = null;
- String args = null;
- idx = fl.indexOf( ':' );
- if( idx > 0 ) {
- name = fl.substring(1,idx);
- args = fl.substring(idx+1,fl.length()-1);
- }
- else {
- name = fl.substring(1,fl.length()-1 );
- }
-
- TransformerFactory factory = req.getCore().getTransformerFactory( name );
- if( factory != null ) {
- augmenters.addTransformer( factory.create(as==null?fl:as, args, req) );
- continue;
- }
- else {
- // unknown field? field that starts with [ and ends with ]?
- }
- }
-
- // If it has a ( it may be a FunctionQuery
- else if( StringUtils.contains(fl, '(' ) ) {
- try {
- QParser parser = QParser.getParser(fl, FunctionQParserPlugin.NAME, req);
- Query q = null;
- ValueSource vs = null;
-
- if (parser instanceof FunctionQParser) {
- FunctionQParser fparser = (FunctionQParser)parser;
- fparser.setParseMultipleSources(false);
- fparser.setParseToEnd(false);
-
- q = fparser.getQuery();
- } else {
- // A QParser that's not for function queries.
- // It must have been specified via local params.
- q = parser.getQuery();
- assert parser.getLocalParams() != null;
- }
-
- if (q instanceof FunctionQuery) {
- vs = ((FunctionQuery)q).getValueSource();
- } else {
- vs = new QueryValueSource(q, 0.0f);
- }
-
- okFieldNames.add( fl );
- okFieldNames.add( as );
- augmenters.addTransformer( new ValueSourceAugmenter( as==null?fl:as, parser, vs ) );
- continue;
- }
- catch (Exception e) {
- // Its OK... could just be a wierd field name
- }
- }
-
- // TODO? support fancy globs?
- else if( fl.endsWith( "*" ) || fl.startsWith( "*" ) ) {
- globs.add( fl );
- continue;
- }
-
- fields.add( fl ); // need to put in the map to maintain order for things like CSVResponseWriter
- okFieldNames.add( fl );
- okFieldNames.add( as );
-
- if( SCORE.equals(fl)) {
- _wantsScore = true;
- augmenters.addTransformer( new ScoreAugmenter( as==null?fl:as ) );
+ name = fl.substring(1,idx);
+ args = fl.substring(idx+1,fl.length()-1);
}
else {
- // it is a normal field
+ name = fl.substring(1,fl.length()-1 );
}
+
+ TransformerFactory factory = req.getCore().getTransformerFactory( name );
+ if( factory != null ) {
+ augmenters.addTransformer( factory.create(display==null?fl:display, args, req) );
+ return;
+ }
+ else {
+ // unknown field? field that starts with [ and ends with ]?
+ }
+ }
+
+ // If it has a ( it may be a FunctionQuery
+ else if( StringUtils.contains(fl, '(' ) ) {
+ try {
+ QParser parser = QParser.getParser(fl, FunctionQParserPlugin.NAME, req);
+ Query q = null;
+ ValueSource vs = null;
+
+ if (parser instanceof FunctionQParser) {
+ FunctionQParser fparser = (FunctionQParser)parser;
+ fparser.setParseMultipleSources(false);
+ fparser.setParseToEnd(false);
+
+ q = fparser.getQuery();
+ } else {
+ // A QParser that's not for function queries.
+ // It must have been specified via local params.
+ q = parser.getQuery();
+ assert parser.getLocalParams() != null;
+ }
+
+ if (q instanceof FunctionQuery) {
+ vs = ((FunctionQuery)q).getValueSource();
+ } else {
+ vs = new QueryValueSource(q, 0.0f);
+ }
+
+ okFieldNames.add( fl );
+ okFieldNames.add( display );
+ augmenters.addTransformer( new ValueSourceAugmenter( display==null?fl:display, parser, vs ) );
+ return;
+ }
+ catch (Exception e) {
+ // Its OK... could just be a wierd field name
+ }
+ }
+
+ // TODO? support fancy globs?
+ else if( fl.endsWith( "*" ) || fl.startsWith( "*" ) ) {
+ globs.add( fl );
+ return;
+ }
+
+ fields.add( fl ); // need to put in the map to maintain order for things like CSVResponseWriter
+ okFieldNames.add( fl );
+ okFieldNames.add( display );
+
+ if( SCORE.equals(fl)) {
+ _wantsScore = true;
+ augmenters.addTransformer( new ScoreAugmenter( display==null?fl:display ) );
+ }
+ else {
+ // it is a normal field
}
}
diff --git a/solr/src/test/org/apache/solr/ConvertedLegacyTest.java b/solr/src/test/org/apache/solr/ConvertedLegacyTest.java
index 92cdcfc..5f731fc 100644
--- a/solr/src/test/org/apache/solr/ConvertedLegacyTest.java
+++ b/solr/src/test/org/apache/solr/ConvertedLegacyTest.java
@@ -1114,7 +1114,8 @@
assertQ(req("id:44")
);
args = new HashMap<String,String>();
- args.put("fl","fname_s,arr_f ");
+ args.put("fl","fname_s,arr_f "); /// space should include score?
+ args.put("fl","fname_s,arr_f,score"); // really? is this what we want in 4.0?
req = new LocalSolrQueryRequest(h.getCore(), "id:44",
"standard", 0, 10, args);
assertQ(req
@@ -1133,7 +1134,7 @@
// test addition of score field
args = new HashMap<String,String>();
- args.put("fl","score ");
+ args.put("fl","score ");
req = new LocalSolrQueryRequest(h.getCore(), "id:44",
"standard", 0, 10, args);
assertQ(req
diff --git a/solr/src/test/org/apache/solr/client/solrj/SolrExampleTests.java b/solr/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
index fe04936..5d3b01b 100644
--- a/solr/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
+++ b/solr/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
@@ -405,7 +405,7 @@
SolrQuery query = new SolrQuery();
query.setQuery( "*:*" );
- query.set( CommonParams.FL, "id,price,[docid],[explain:nl],score,[value:aaa] AS aaa,[value:int:10] AS ten" );
+ query.set( CommonParams.FL, "id,price,[docid],[explain nl],score,aaa:[value aaa],ten:[value int 10]" );
query.addSortField( "price", SolrQuery.ORDER.asc );
QueryResponse rsp = server.query( query );
@@ -423,8 +423,10 @@
int id2 = (Integer)out2.getFieldValue( "[docid]" );
assertTrue( "should be bigger ["+id1+","+id2+"]", id2 > id1 );
+ System.out.println( out1 );
+
// The score from explain should be the same as the score
- NamedList explain = (NamedList)out1.getFieldValue( "[explain:nl]" );
+ NamedList explain = (NamedList)out1.getFieldValue( "[explain nl]" );
assertEquals( out1.get( "score"), explain.get( "value" ) );
// Augmented _value_ with alias
diff --git a/solr/src/test/org/apache/solr/search/TestSolrQueryParser.java b/solr/src/test/org/apache/solr/search/TestSolrQueryParser.java
index dec3319..9f3b605 100644
--- a/solr/src/test/org/apache/solr/search/TestSolrQueryParser.java
+++ b/solr/src/test/org/apache/solr/search/TestSolrQueryParser.java
@@ -17,7 +17,9 @@
package org.apache.solr.search;
import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.response.transform.DocTransformers;
import org.apache.solr.response.transform.ScoreAugmenter;
+import org.apache.solr.response.transform.ValueAugmenterFactory;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -64,11 +66,18 @@
assertTrue( rf.wantsField( "xxx" ) );
assertTrue( rf.wantsAllFields() );
assertNull( rf.getTransformer() );
+
+ // legacy, score is *,score
+ rf = new ReturnFields( req("fl", "score ") );
+ assertTrue( rf.wantsScore() );
+ assertTrue( rf.wantsAllFields() );
+ assertTrue( rf.wantsField( "score" ) );
- rf = new ReturnFields( req("fl", "[explain]") );
- assertFalse( rf.wantsScore() );
+ rf = new ReturnFields( req("fl", "[explain],score") );
+ assertTrue( rf.wantsScore() );
assertFalse( rf.wantsField( "id" ) );
- assertEquals( "[explain]", rf.getTransformer().getName() );
+ assertEquals( "Transformers[[explain],score]", rf.getTransformer().getName() );
+ assertTrue( rf.wantsScore() );
// Check that we want wildcards
rf = new ReturnFields( req("fl", "id,aaa*,*bbb") );
@@ -79,14 +88,22 @@
assertFalse( rf.wantsField( "bb" ) );
// Check pseudo fiels are replaced
- rf = new ReturnFields( req("fl", "price", "fl.pseudo", "price:[value:10]" ) );
+ rf = new ReturnFields( req("fl", "price", "fl.pseudo.price", "[value 10]", "fl.pseudo", "true" ) );
assertTrue( rf.wantsField( "price" ) );
assertEquals( "price", rf.getTransformer().getName() );
+ assertTrue( rf.getTransformer().getClass().getName().indexOf( "ValueAugmenter" ) > 0 );
- rf = new ReturnFields( req("fl", "price AS xxx,name AS yyy", "fl.pseudo", "price:[value:10]" ) );
+ rf = new ReturnFields( req("fl", "xxx:price,yyy:name", "fl.pseudo.price", "[value 10]", "fl.pseudo", "true" ) );
assertTrue( rf.wantsField( "price" ) );
assertTrue( rf.wantsField( "yyy" ) );
assertTrue( rf.getLuceneFieldNames().contains("name") );
assertEquals( "xxx", rf.getTransformer().getName() );
+ assertTrue( rf.getTransformer().getClass().getName().indexOf( "ValueAugmenter" ) > 0 );
+
+ // multiple transformers
+ rf = new ReturnFields( req("fl", "[value hello],[explain]" ) );
+ DocTransformers tx = (DocTransformers)rf.getTransformer(); // will throw exception
+ assertTrue( tx.get(0).getClass().getName().indexOf( "ValueAugmenter" ) > 0 );
+ assertTrue( tx.get(1).getClass().getName().indexOf( "ExplainAugmenter" ) > 0 );
}
}