mirror of https://github.com/apache/lucene.git
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:
parent
bbcea96908
commit
b831a14ba5
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -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
|
||||
* */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
@ -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']"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue