SOLR-7888: Analyzing suggesters can now filter suggestions by a context field

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1707907 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jan Høydahl 2015-10-10 19:01:59 +00:00
parent bbcea96908
commit b831a14ba5
12 changed files with 521 additions and 31 deletions

View File

@ -24,6 +24,7 @@ import java.util.Comparator;
import java.util.List;
import java.util.Set;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.spell.Dictionary;
import org.apache.lucene.store.DataInput;
import org.apache.lucene.store.DataOutput;
@ -251,6 +252,22 @@ public abstract class Lookup implements Accountable {
*/
public abstract List<LookupResult> lookup(CharSequence key, Set<BytesRef> contexts, boolean onlyMorePopular, int num) throws IOException;
/**
* Look up a key and return possible completion for this key.
* This needs to be overridden by all implementing classes as the default implementation just returns null
*
* @param key the lookup key
* @param contextFilerQuery A query for further filtering the result of the key lookup
* @param num maximum number of results to return
* @param allTermsRequired true is all terms are required
* @param doHighlight set to true if key should be highlighted
* @return a list of suggestions/completions. The default implementation returns null, meaning each @Lookup implementation should override this and provide their own implementation
* @throws IOException when IO exception occurs
*/
public List<LookupResult> lookup(CharSequence key, BooleanQuery contextFilerQuery, int num, boolean allTermsRequired, boolean doHighlight) throws IOException{
return null;
}
/**
* Persist the constructed lookup data to a directory. Optional operation.
* @param output {@link DataOutput} to write the data to.

View File

@ -176,6 +176,8 @@ New Features
* SOLR-7858: Add links between original and new Admin UIs (Upayavira)
* SOLR-7888: Analyzing suggesters can now filter suggestions by a context field (Arcadius Ahouansou, janhoy)
Bug Fixes
----------------------

View File

@ -186,7 +186,7 @@ public class SuggestComponent extends SearchComponent implements SolrCoreAware,
rb.rsp.add("command", (!reloadAll) ? "reload" : "reloadAll");
}
}
/** Dispatch shard request in <code>STAGE_EXECUTE_QUERY</code> stage */
@Override
public int distributedProcess(ResponseBuilder rb) {
@ -238,11 +238,21 @@ public class SuggestComponent extends SearchComponent implements SolrCoreAware,
query = params.get(CommonParams.Q);
}
}
if (query != null) {
int count = params.getInt(SUGGEST_COUNT, 1);
SuggesterOptions options = new SuggesterOptions(new CharsRef(query), count);
Map<String, SimpleOrderedMap<NamedList<Object>>> namedListResults =
boolean highlight = params.getBool(SUGGEST_HIGHLIGHT, false);
boolean allTermsRequired = params.getBool(SUGGEST_ALL_TERMS_REQUIRED, true);
String contextFilter = params.get(SUGGEST_CONTEXT_FILTER_QUERY);
if (contextFilter != null) {
contextFilter = contextFilter.trim();
if (contextFilter.length() == 0) {
contextFilter = null;
}
}
SuggesterOptions options = new SuggesterOptions(new CharsRef(query), count, contextFilter, allTermsRequired, highlight);
Map<String, SimpleOrderedMap<NamedList<Object>>> namedListResults =
new HashMap<>();
for (SolrSuggester suggester : querySuggesters) {
SuggesterResult suggesterResult = suggester.getSuggestions(options);
@ -251,7 +261,7 @@ public class SuggestComponent extends SearchComponent implements SolrCoreAware,
rb.rsp.add(SuggesterResultLabels.SUGGEST, namedListResults);
}
}
/**
* Used in Distributed Search, merges the suggestion results from every shard
* */

View File

@ -33,6 +33,8 @@ public class DocumentDictionaryFactory extends DictionaryFactory {
public static final String PAYLOAD_FIELD = "payloadField";
public static final String CONTEXT_FIELD = "contextField";
@Override
public Dictionary create(SolrCore core, SolrIndexSearcher searcher) {
if(params == null) {
@ -42,12 +44,13 @@ public class DocumentDictionaryFactory extends DictionaryFactory {
String field = (String) params.get(FIELD);
String weightField = (String) params.get(WEIGHT_FIELD);
String payloadField = (String) params.get(PAYLOAD_FIELD);
String contextField = (String) params.get(CONTEXT_FIELD);
if (field == null) {
throw new IllegalArgumentException(FIELD + " is a mandatory parameter");
}
return new DocumentDictionary(searcher.getIndexReader(), field, weightField, payloadField);
return new DocumentDictionary(searcher.getIndexReader(), field, weightField, payloadField, contextField);
}
}

View File

@ -23,13 +23,22 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardTokenizerFactory;
import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.spell.Dictionary;
import org.apache.lucene.search.suggest.Lookup;
import org.apache.lucene.search.suggest.Lookup.LookupResult;
import org.apache.lucene.util.Accountable;
import org.apache.lucene.util.IOUtils;
import org.apache.solr.analysis.TokenizerChain;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.CloseHook;
import org.apache.solr.core.SolrCore;
@ -38,6 +47,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.common.params.CommonParams.NAME;
import static org.apache.solr.spelling.suggest.fst.AnalyzingInfixLookupFactory.CONTEXTS_FIELD_NAME;
/**
* Responsible for loading the lookup and dictionary Implementations specified by
@ -61,7 +71,7 @@ public class SolrSuggester implements Accountable {
/** Fully-qualified class of the {@link Dictionary} implementation */
public static final String DICTIONARY_IMPL = "dictionaryImpl";
/**
* Name of the location where to persist the dictionary. If this location
* is relative then the data will be stored under the core's dataDir. If this
@ -81,8 +91,9 @@ public class SolrSuggester implements Accountable {
private LookupFactory factory;
private DictionaryFactory dictionaryFactory;
/**
private Analyzer contextFilterQueryAnalyzer;
/**
* Uses the <code>config</code> and the <code>core</code> to initialize the underlying
* Lucene suggester
* */
@ -101,6 +112,9 @@ public class SolrSuggester implements Accountable {
lookupImpl = LookupFactory.DEFAULT_FILE_BASED_DICT;
LOG.info("No " + LOOKUP_IMPL + " parameter was provided falling back to " + lookupImpl);
}
contextFilterQueryAnalyzer = new TokenizerChain(new StandardTokenizerFactory(Collections.EMPTY_MAP), null);
// initialize appropriate lookup instance
factory = core.getResourceLoader().newInstance(lookupImpl, LookupFactory.class);
lookup = factory.create(config, core);
@ -146,7 +160,7 @@ public class SolrSuggester implements Accountable {
DictionaryFactory.DEFAULT_FILE_BASED_DICT;
LOG.info("No " + DICTIONARY_IMPL + " parameter was provided falling back to " + dictionaryImpl);
}
dictionaryFactory = core.getResourceLoader().newInstance(dictionaryImpl, DictionaryFactory.class);
dictionaryFactory.setParams(config);
LOG.info("Dictionary loaded with params: " + config);
@ -212,11 +226,41 @@ public class SolrSuggester implements Accountable {
}
SuggesterResult res = new SuggesterResult();
List<LookupResult> suggestions = lookup.lookup(options.token, false, options.count);
List<LookupResult> suggestions;
if(options.contextFilterQuery == null){
//TODO: this path needs to be fixed to accept query params to override configs such as allTermsRequired, highlight
suggestions = lookup.lookup(options.token, false, options.count);
} else {
BooleanQuery query = parseContextFilterQuery(options.contextFilterQuery);
suggestions = lookup.lookup(options.token, query, options.count, options.allTermsRequired, options.highlight);
if(suggestions == null){
// Context filtering not supported/configured by lookup
// Silently ignore filtering and serve a result by querying without context filtering
LOG.debug("Context Filtering Query not supported by {}", lookup.getClass());
suggestions = lookup.lookup(options.token, false, options.count);
}
}
res.add(getName(), options.token.toString(), suggestions);
return res;
}
private BooleanQuery parseContextFilterQuery(String contextFilter) {
if(contextFilter == null){
return null;
}
Query query = null;
try {
query = new StandardQueryParser(contextFilterQueryAnalyzer).parse(contextFilter, CONTEXTS_FIELD_NAME);
if (query instanceof BooleanQuery) {
return (BooleanQuery) query;
}
return new BooleanQuery.Builder().add(query, BooleanClause.Occur.MUST).build();
} catch (QueryNodeException e) {
throw new IllegalArgumentException("Failed to parse query: " + query);
}
}
/** Returns the unique name of the suggester */
public String getName() {
return name;

View File

@ -30,9 +30,21 @@ public class SuggesterOptions {
/** Number of suggestions requested */
int count;
public SuggesterOptions(CharsRef token, int count) {
/** A Solr or Lucene query for filtering suggestions*/
String contextFilterQuery;
/** Are all terms required?*/
boolean allTermsRequired;
/** Highlight term in results?*/
boolean highlight;
public SuggesterOptions(CharsRef token, int count, String contextFilterQuery, boolean allTermsRequired, boolean highlight) {
this.token = token;
this.count = count;
this.contextFilterQuery = contextFilterQuery;
this.allTermsRequired = allTermsRequired;
this.highlight = highlight;
}
}

View File

@ -66,4 +66,21 @@ public interface SuggesterParams {
* This parameter does not need any suggest dictionary names to be specified
*/
public static final String SUGGEST_RELOAD_ALL = SUGGEST_PREFIX + "reloadAll";
/**
* contextFilterQuery to use for filtering the result of the suggestion
*/
public static final String SUGGEST_CONTEXT_FILTER_QUERY = SUGGEST_PREFIX + "cfq";
/**
* Whether keyword should be highlighted in result or not
*/
public static final String SUGGEST_HIGHLIGHT = SUGGEST_PREFIX + "highlight";
/**
* Whether all terms are required or not
*/
public static final String SUGGEST_ALL_TERMS_REQUIRED = SUGGEST_PREFIX + "allTermsRequired";
}

View File

@ -75,6 +75,11 @@ public class AnalyzingInfixLookupFactory extends LookupFactory {
* File name for the automaton.
*/
private static final String FILENAME = "iwfsta.bin";
/**
* Clone of CONTEXTS_FIELD_NAME in AnalyzingInfixSuggester
*/
public static final String CONTEXTS_FIELD_NAME = "contexts";
@Override
@ -110,7 +115,7 @@ public class AnalyzingInfixLookupFactory extends LookupFactory {
boolean highlight = params.get(HIGHLIGHT) != null
? Boolean.getBoolean(params.get(HIGHLIGHT).toString())
: AnalyzingInfixSuggester.DEFAULT_HIGHLIGHT;
: AnalyzingInfixSuggester.DEFAULT_HIGHLIGHT;
try {
return new AnalyzingInfixSuggester(FSDirectory.open(new File(indexPath).toPath()), indexAnalyzer,

View File

@ -0,0 +1,121 @@
<?xml version="1.0" ?>
<!--
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.
-->
<config>
<xi:include href="solrconfig.snippet.randomindexconfig.xml" xmlns:xi="http://www.w3.org/2001/XInclude"/>
<luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
<!-- The DirectoryFactory to use for indexes.
solr.StandardDirectoryFactory, the default, is filesystem based.
solr.RAMDirectoryFactory is memory based and not persistent. -->
<dataDir>${solr.data.dir:}</dataDir>
<directoryFactory name="DirectoryFactory" class="solr.NRTCachingDirectoryFactory"/>
<updateHandler class="solr.DirectUpdateHandler2"/>
<requestHandler name="standard" class="solr.StandardRequestHandler" />
<searchComponent class="solr.SuggestComponent" name="suggest">
<!--Suggest Component for context filtering test -->
<lst name="suggester">
<str name="name">suggest_blended_infix_suggester</str>
<str name="lookupImpl">BlendedInfixLookupFactory</str>
<str name="dictionaryImpl">DocumentDictionaryFactory</str>
<str name="field">cat</str>
<str name="weightField">price</str>
<str name="contextField">my_contexts_t</str>
<str name="suggestAnalyzerFieldType">text</str>
<str name="buildOnCommit">false</str>
<str name="buildOnStartup">false</str>
<str name="storeDir">suggest_blended_infix_suggester</str>
<str name="indexPath">suggest_blended_infix_suggester</str>
<str name="highlight">false</str>
</lst>
<lst name="suggester">
<str name="name">suggest_blended_infix_suggester_string</str>
<str name="lookupImpl">BlendedInfixLookupFactory</str>
<str name="dictionaryImpl">DocumentDictionaryFactory</str>
<str name="field">cat</str>
<str name="weightField">price</str>
<str name="contextField">my_contexts_s</str>
<str name="suggestAnalyzerFieldType">text</str>
<str name="buildOnCommit">false</str>
<str name="buildOnStartup">false</str>
<str name="storeDir">suggest_blended_infix_suggester_string</str>
<str name="indexPath">suggest_blended_infix_suggester_string</str>
<str name="highlight">false</str>
</lst>
<lst name="suggester">
<str name="name">suggest_lookup_has_no_context_implementation</str>
<str name="lookupImpl">AnalyzingLookupFactory</str>
<str name="dictionaryImpl">DocumentDictionaryFactory</str>
<str name="field">cat</str>
<str name="weightField">price</str>
<str name="suggestAnalyzerFieldType">text</str>
<str name="buildOnCommit">false</str>
<str name="buildOnStartup">false</str>
<str name="storeDir">suggest_lookup_has_no_context_implementation</str>
<str name="indexPath">suggest_lookup_has_no_context_implementation</str>
<str name="highlight">false</str>
</lst>
<lst name="suggester">
<str name="name">suggest_context_filtering_not_implemented</str>
<str name="lookupImpl">AnalyzingLookupFactory</str>
<str name="dictionaryImpl">DocumentDictionaryFactory</str>
<str name="field">cat</str>
<str name="weightField">price</str>
<str name="suggestAnalyzerFieldType">text</str>
<str name="buildOnCommit">false</str>
<str name="buildOnStartup">false</str>
<str name="contextField">my_contexts_t</str>
<str name="storeDir">suggest_context_filtering_not_implemented</str>
<str name="indexPath">suggest_context_filtering_not_implemented</str>
<str name="highlight">false</str>
</lst>
<lst name="suggester">
<str name="name">suggest_context_implemented_but_not_configured</str>
<str name="lookupImpl">BlendedInfixLookupFactory</str>
<str name="dictionaryImpl">DocumentDictionaryFactory</str>
<str name="field">cat</str>
<str name="weightField">price</str>
<str name="suggestAnalyzerFieldType">text</str>
<str name="buildOnCommit">false</str>
<str name="buildOnStartup">false</str>
<str name="storeDir">suggest_context_implemented_but_not_configured</str>
<str name="indexPath">suggest_context_implemented_but_not_configured</str>
<str name="highlight">false</str>
</lst>
</searchComponent>
<requestHandler name="/suggest" class="org.apache.solr.handler.component.SearchHandler">
<lst name="defaults">
<str name="suggest">true</str>
<str name="suggest.count">5</str>
</lst>
<arr name="components">
<str>suggest</str>
</arr>
</requestHandler>
<query><useColdSearcher>false</useColdSearcher></query>
</config>

View File

@ -0,0 +1,258 @@
package org.apache.solr.handler.component;
/*
* 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.SolrTestCaseJ4;
import org.apache.solr.spelling.suggest.SuggesterParams;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import static org.hamcrest.core.Is.is;
public class SuggestComponentContextFilterQueryTest extends SolrTestCaseJ4 {
static String rh = "/suggest";
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig-suggestercomponent-context-filter-query.xml", "schema.xml");
}
@Override
public void setUp() throws Exception {
super.setUp();
assertU(delQ("*:*"));
// id, cat, price, weight, contexts
assertU(adoc("id", "0", "cat", "This is a title", "price", "5", "weight", "10", "my_contexts_t", "ctx1"));
assertU(adoc("id", "1", "cat", "This is another title", "price", "10", "weight", "10", "my_contexts_t", "ctx1"));
assertU(adoc("id", "7", "cat", "example with ctx1 at 40", "price", "40", "weight", "30", "my_contexts_t", "ctx1"));
assertU(adoc("id", "8", "cat", "example with ctx2 and ctx3 at 45", "price", "45", "weight", "30", "my_contexts_t", "CTX2", "my_contexts_t", "CTX3"));
assertU(adoc("id", "9", "cat", "example with ctx4 at 50 using my_contexts_s", "price", "50", "weight", "40", "my_contexts_s", "ctx4"));
assertU((commit()));
waitForWarming();
}
@Test
public void testContextFilterParamIsIgnoredWhenContextIsNotImplemented() throws Exception {
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_lookup_has_no_context_implementation",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx1",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_lookup_has_no_context_implementation']/lst[@name='examp']/int[@name='numFound'][.='3']",
"//lst[@name='suggest']/lst[@name='suggest_lookup_has_no_context_implementation']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx4 at 50 using my_contexts_s']",
"//lst[@name='suggest']/lst[@name='suggest_lookup_has_no_context_implementation']/lst[@name='examp']/arr[@name='suggestions']/lst[2]/str[@name='term'][.='example with ctx2 and ctx3 at 45']",
"//lst[@name='suggest']/lst[@name='suggest_lookup_has_no_context_implementation']/lst[@name='examp']/arr[@name='suggestions']/lst[3]/str[@name='term'][.='example with ctx1 at 40']"
);
}
@Test
public void testContextFilteringIsIgnoredWhenContextIsImplementedButNotConfigured() throws Exception {
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_context_implemented_but_not_configured",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_context_implemented_but_not_configured']/lst[@name='examp']/int[@name='numFound'][.='3']",
"//lst[@name='suggest']/lst[@name='suggest_context_implemented_but_not_configured']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx4 at 50 using my_contexts_s']",
"//lst[@name='suggest']/lst[@name='suggest_context_implemented_but_not_configured']/lst[@name='examp']/arr[@name='suggestions']/lst[2]/str[@name='term'][.='example with ctx2 and ctx3 at 45']",
"//lst[@name='suggest']/lst[@name='suggest_context_implemented_but_not_configured']/lst[@name='examp']/arr[@name='suggestions']/lst[3]/str[@name='term'][.='example with ctx1 at 40']"
);
}
@Test
public void testBuildThrowsIllegalArgumentExceptionWhenContextIsConfiguredButNotImplemented() throws Exception {
try {
assertQ(
req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_context_filtering_not_implemented",
SuggesterParams.SUGGEST_Q, "examp")
,
""
);
fail("Expecting exception because ");
} catch (RuntimeException e) {
Throwable cause = e.getCause();
assertTrue(cause instanceof IllegalArgumentException);
assertThat(cause.getMessage(), is("this suggester doesn't support contexts"));
}
// When not building, no exception is thrown
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "false",
SuggesterParams.SUGGEST_DICT, "suggest_context_filtering_not_implemented",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_context_filtering_not_implemented']/lst[@name='examp']/int[@name='numFound'][.='0']"
);
}
@Test
public void testContextFilterIsTrimmed() throws Exception {
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, " ", //trimmed to null... just as if there was no context filter param
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='3']"
);
}
public void testExplicitFieldedQuery() throws Exception {
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "contexts:ctx1",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='1']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx1 at 40']"
);
}
public void testContextFilterOK() throws Exception {
//No filtering
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='3']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx4 at 50 using my_contexts_s']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[2]/str[@name='term'][.='example with ctx2 and ctx3 at 45']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[3]/str[@name='term'][.='example with ctx1 at 40']"
);
//TermQuery
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx1",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='1']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx1 at 40']"
);
//OR BooleanQuery
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx1 OR CTX2",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='2']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx2 and ctx3 at 45']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[2]/str[@name='term'][.='example with ctx1 at 40']"
);
//AND BooleanQuery
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "CTX2 AND CTX3",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='1']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx2 and ctx3 at 45']");
//PrefixQuery
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx*",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='1']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx1 at 40']"
);
//RangeQuery
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "[* TO *]",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='2']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx2 and ctx3 at 45']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[2]/str[@name='term'][.='example with ctx1 at 40']"
);
//WildcardQuery
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "c*1",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='1']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='example with ctx1 at 40']");
}
@Test
public void testStringContext(){
//Here, the context field is a string, so it's case sensitive
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester_string",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "Ctx4",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester_string']/lst[@name='examp']/int[@name='numFound'][.='0']");
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester_string",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx4",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester_string']/lst[@name='examp']/int[@name='numFound'][.='1']");
}
@Test
public void testContextFilterOnInvalidFieldGivesNoSuggestions() throws Exception {
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "some_invalid_context_field:some_invalid_value",
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='0']");
}
@Test
public void testContextFilterUsesAnalyzer() throws Exception {
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "CTx1", // Will not match due to case
SuggesterParams.SUGGEST_Q, "examp"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='examp']/int[@name='numFound'][.='0']");
}
@Ignore// TODO: SOLR-7964
@Test
public void testContextFilterWithHighlight() throws Exception {
assertQ(req("qt", rh,
SuggesterParams.SUGGEST_BUILD, "true",
SuggesterParams.SUGGEST_DICT, "suggest_blended_infix_suggester",
SuggesterParams.SUGGEST_CONTEXT_FILTER_QUERY, "ctx1",
SuggesterParams.SUGGEST_HIGHLIGHT, "true",
SuggesterParams.SUGGEST_Q, "example"),
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='example']/int[@name='numFound'][.='1']",
"//lst[@name='suggest']/lst[@name='suggest_blended_infix_suggester']/lst[@name='example']/arr[@name='suggestions']/lst[1]/str[@name='term'][.='<b>example</b> data']"
);
}
}

View File

@ -522,20 +522,4 @@ public class SuggestComponentTest extends SolrTestCaseJ4 {
);
}
private void waitForWarming() throws InterruptedException {
RefCounted<SolrIndexSearcher> registeredSearcher = h.getCore().getRegisteredSearcher();
RefCounted<SolrIndexSearcher> newestSearcher = h.getCore().getNewestSearcher(false);;
while (registeredSearcher == null || registeredSearcher.get() != newestSearcher.get()) {
if (registeredSearcher != null) {
registeredSearcher.decref();
}
newestSearcher.decref();
Thread.sleep(50);
registeredSearcher = h.getCore().getRegisteredSearcher();
newestSearcher = h.getCore().getNewestSearcher(false);
}
registeredSearcher.decref();
newestSearcher.decref();
}
}

View File

@ -98,6 +98,7 @@ import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.servlet.DirectSolrConnection;
import org.apache.solr.util.AbstractSolrTestCase;
import org.apache.solr.util.DateFormatUtil;
import org.apache.solr.util.RefCounted;
import org.apache.solr.util.RevertDefaultThreadHandlerRule;
import org.apache.solr.util.SSLTestConfig;
import org.apache.solr.util.TestHarness;
@ -2117,4 +2118,20 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase {
return result;
}
protected void waitForWarming() throws InterruptedException {
RefCounted<SolrIndexSearcher> registeredSearcher = h.getCore().getRegisteredSearcher();
RefCounted<SolrIndexSearcher> newestSearcher = h.getCore().getNewestSearcher(false);
;
while (registeredSearcher == null || registeredSearcher.get() != newestSearcher.get()) {
if (registeredSearcher != null) {
registeredSearcher.decref();
}
newestSearcher.decref();
Thread.sleep(50);
registeredSearcher = h.getCore().getRegisteredSearcher();
newestSearcher = h.getCore().getNewestSearcher(false);
}
registeredSearcher.decref();
newestSearcher.decref();
}
}