/**
* 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.component;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.QueryElevationParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.component.QueryElevationComponent.ElevationObj;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
public class QueryElevationComponentTest extends SolrTestCaseJ4 {
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig-elevate.xml","schema12.xml");
}
@Before
@Override
public void setUp() throws Exception {
super.setUp();
clearIndex();
assertU(commit());
}
@Test
public void testInterface() throws Exception
{
SolrCore core = h.getCore();
NamedList<String> args = new NamedList<String>();
args.add( QueryElevationComponent.FIELD_TYPE, "string" );
args.add( QueryElevationComponent.CONFIG_FILE, "elevate.xml" );
QueryElevationComponent comp = new QueryElevationComponent();
comp.init( args );
comp.inform( core );
IndexReader reader = core.getSearcher().get().getReader();
Map<String, ElevationObj> map = comp.getElevationMap( reader, core );
// Make sure the boosts loaded properly
assertEquals( 3, map.size() );
assertEquals( 1, map.get( "XXXX" ).priority.size() );
assertEquals( 2, map.get( "YYYY" ).priority.size() );
assertEquals( 3, map.get( "ZZZZ" ).priority.size() );
assertEquals( null, map.get( "xxxx" ) );
assertEquals( null, map.get( "yyyy" ) );
assertEquals( null, map.get( "zzzz" ) );
// Now test the same thing with a lowercase filter: 'lowerfilt'
args = new NamedList<String>();
args.add( QueryElevationComponent.FIELD_TYPE, "lowerfilt" );
args.add( QueryElevationComponent.CONFIG_FILE, "elevate.xml" );
comp = new QueryElevationComponent();
comp.init( args );
comp.inform( core );
map = comp.getElevationMap( reader, core );
assertEquals( 3, map.size() );
assertEquals( null, map.get( "XXXX" ) );
assertEquals( null, map.get( "YYYY" ) );
assertEquals( null, map.get( "ZZZZ" ) );
assertEquals( 1, map.get( "xxxx" ).priority.size() );
assertEquals( 2, map.get( "yyyy" ).priority.size() );
assertEquals( 3, map.get( "zzzz" ).priority.size() );
assertEquals( "xxxx", comp.getAnalyzedQuery( "XXXX" ) );
assertEquals( "xxxxyyyy", comp.getAnalyzedQuery( "XXXX YYYY" ) );
}
@Test
public void testEmptyQuery() throws Exception {
SolrCore core = h.getCore();
//String query = "title:ipod";
Map<String,String> args = new HashMap<String, String>();
args.put( "q.alt", "*:*" );
args.put( "defType", "dismax");
args.put( CommonParams.QT, "/elevate" );
//args.put( CommonParams.FL, "id,title,score" );
SolrQueryRequest req = new LocalSolrQueryRequest( core, new MapSolrParams( args) );
assertQ("Make sure QEC handles null queries", req, "//*[@numFound='0']");
}
@Test
public void testSorting() throws IOException
{
SolrCore core = h.getCore();
assertU(adoc("id", "a", "title", "ipod", "str_s", "a" ));
assertU(adoc("id", "b", "title", "ipod ipod", "str_s", "b" ));
assertU(adoc("id", "c", "title", "ipod ipod ipod", "str_s", "c" ));
assertU(adoc("id", "x", "title", "boosted", "str_s", "x" ));
assertU(adoc("id", "y", "title", "boosted boosted", "str_s", "y" ));
assertU(adoc("id", "z", "title", "boosted boosted boosted", "str_s", "z" ));
assertU(commit());
String query = "title:ipod";
Map<String,String> args = new HashMap<String, String>();
args.put( CommonParams.Q, query );
args.put( CommonParams.QT, "/elevate" );
args.put( CommonParams.FL, "id,score" );
args.put( "indent", "true" );
//args.put( CommonParams.FL, "id,title,score" );
SolrQueryRequest req = new LocalSolrQueryRequest( core, new MapSolrParams( args) );
assertQ("Make sure standard sort works as expected", req
,"//*[@numFound='3']"
,"//result/doc[1]/str[@name='id'][.='a']"
,"//result/doc[2]/str[@name='id'][.='b']"
,"//result/doc[3]/str[@name='id'][.='c']"
);
// Explicitly set what gets boosted
IndexReader reader = core.getSearcher().get().getReader();
QueryElevationComponent booster = (QueryElevationComponent)core.getSearchComponent( "elevate" );
booster.elevationCache.clear();
booster.setTopQueryResults( reader, query, new String[] { "x", "y", "z" }, null );
assertQ("All six should make it", req
,"//*[@numFound='6']"
,"//result/doc[1]/str[@name='id'][.='x']"
,"//result/doc[2]/str[@name='id'][.='y']"
,"//result/doc[3]/str[@name='id'][.='z']"
,"//result/doc[4]/str[@name='id'][.='a']"
,"//result/doc[5]/str[@name='id'][.='b']"
,"//result/doc[6]/str[@name='id'][.='c']"
);
booster.elevationCache.clear();
// now switch the order:
booster.setTopQueryResults( reader, query, new String[] { "a", "x" }, null );
assertQ("All four should make it", req
,"//*[@numFound='4']"
,"//result/doc[1]/str[@name='id'][.='a']"
,"//result/doc[2]/str[@name='id'][.='x']"
,"//result/doc[3]/str[@name='id'][.='b']"
,"//result/doc[4]/str[@name='id'][.='c']"
);
// Test reverse sort
args.put( CommonParams.SORT, "score asc" );
assertQ("All four should make it", req
,"//*[@numFound='4']"
,"//result/doc[4]/str[@name='id'][.='a']"
,"//result/doc[3]/str[@name='id'][.='x']"
,"//result/doc[2]/str[@name='id'][.='b']"
,"//result/doc[1]/str[@name='id'][.='c']"
);
// Try normal sort by 'id'
// default 'forceBoost' shoudl be false
assertEquals( false, booster.forceElevation );
args.put( CommonParams.SORT, "str_s asc" );
assertQ( null, req
,"//*[@numFound='4']"
,"//result/doc[1]/str[@name='id'][.='a']"
,"//result/doc[2]/str[@name='id'][.='b']"
,"//result/doc[3]/str[@name='id'][.='c']"
,"//result/doc[4]/str[@name='id'][.='x']"
);
booster.forceElevation = true;
assertQ( null, req
,"//*[@numFound='4']"
,"//result/doc[1]/str[@name='id'][.='a']"
,"//result/doc[2]/str[@name='id'][.='x']"
,"//result/doc[3]/str[@name='id'][.='b']"
,"//result/doc[4]/str[@name='id'][.='c']"
);
//Test exclusive (not to be confused with exclusion)
args.put(QueryElevationParams.EXCLUSIVE, "true");
booster.setTopQueryResults( reader, query, new String[] { "x", "a" }, new String[] {} );
assertQ( null, req
,"//*[@numFound='2']"
,"//result/doc[1]/str[@name='id'][.='x']"
,"//result/doc[2]/str[@name='id'][.='a']"
);
// Test exclusion
booster.elevationCache.clear();
args.remove( CommonParams.SORT );
args.remove( QueryElevationParams.EXCLUSIVE);
booster.setTopQueryResults( reader, query, new String[] { "x" }, new String[] { "a" } );
assertQ( null, req
,"//*[@numFound='3']"
,"//result/doc[1]/str[@name='id'][.='x']"
,"//result/doc[2]/str[@name='id'][.='b']"
,"//result/doc[3]/str[@name='id'][.='c']"
);
}
// write a test file to boost some docs
private void writeFile( File file, String query, String ... ids ) throws Exception
{
PrintWriter out = new PrintWriter( new FileOutputStream( file ) );
out.println( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" );
out.println( "<elevate>" );
out.println( "<query text=\""+query+"\">" );
for( String id : ids ) {
out.println( " <doc id=\""+id+"\"/>" );
}
out.println( "</query>" );
out.println( "</elevate>" );
out.flush();
out.close();
log.info( "OUT:"+file.getAbsolutePath() );
}
@Test
public void testElevationReloading() throws Exception
{
SolrCore core = h.getCore();
String testfile = "data-elevation.xml";
File f = new File( core.getDataDir(), testfile );
writeFile( f, "aaa", "A" );
QueryElevationComponent comp = (QueryElevationComponent)core.getSearchComponent("elevate");
NamedList<String> args = new NamedList<String>();
args.add( QueryElevationComponent.CONFIG_FILE, testfile );
comp.init( args );
comp.inform( core );
IndexReader reader = core.getSearcher().get().getReader();
Map<String, ElevationObj> map = comp.getElevationMap(reader, core);
assertTrue( map.get( "aaa" ).priority.containsKey( new BytesRef("A") ) );
assertNull( map.get( "bbb" ) );
// now change the file
writeFile( f, "bbb", "B" );
assertU(adoc("id", "10000")); // will get same reader if no index change
assertU(commit());
reader = core.getSearcher().get().getReader();
map = comp.getElevationMap(reader, core);
assertNull( map.get( "aaa" ) );
assertTrue( map.get( "bbb" ).priority.containsKey( new BytesRef("B") ) );
}
}