summaryrefslogtreecommitdiff
path: root/core/src/main/java/org/elasticsearch/search/suggest/completion
diff options
context:
space:
mode:
authorAreek Zillur <areek.zillur@elasticsearch.com>2016-04-07 15:34:39 -0400
committerAreek Zillur <areek.zillur@elasticsearch.com>2016-04-25 21:21:56 -0400
commit4a1a03428d7884c185b05c364c6f220237a9567e (patch)
treedce7bfb76bfa033c0961f60a7654426f2f288d26 /core/src/main/java/org/elasticsearch/search/suggest/completion
parentd39eb2d69131d7a6a8bda29dcac31342bb1a712e (diff)
Add bwc support for pre-5.0 completion index
This commit adds support for reading and querying completion fields that were indexed in 2.x
Diffstat (limited to 'core/src/main/java/org/elasticsearch/search/suggest/completion')
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggester.java158
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java136
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionContext.java20
3 files changed, 265 insertions, 49 deletions
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggester.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggester.java
index 8bf35a34b2..16a2804b37 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggester.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggester.java
@@ -18,8 +18,11 @@
*/
package org.elasticsearch.search.suggest.completion;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.ReaderUtil;
+import org.apache.lucene.index.Terms;
import org.apache.lucene.search.BulkScorer;
import org.apache.lucene.search.CollectionTerminatedException;
import org.apache.lucene.search.IndexSearcher;
@@ -29,7 +32,9 @@ import org.apache.lucene.search.suggest.document.CompletionQuery;
import org.apache.lucene.search.suggest.document.TopSuggestDocs;
import org.apache.lucene.search.suggest.document.TopSuggestDocsCollector;
import org.apache.lucene.util.CharsRefBuilder;
+import org.apache.lucene.util.CollectionUtil;
import org.apache.lucene.util.PriorityQueue;
+import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.fielddata.AtomicFieldData;
@@ -42,10 +47,12 @@ import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.Suggester;
import org.elasticsearch.search.suggest.SuggestionBuilder;
+import org.elasticsearch.search.suggest.completion2x.Completion090PostingsFormat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@@ -61,56 +68,119 @@ public class CompletionSuggester extends Suggester<CompletionSuggestionContext>
@Override
protected Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> innerExecute(String name,
final CompletionSuggestionContext suggestionContext, final IndexSearcher searcher, CharsRefBuilder spare) throws IOException {
- final CompletionFieldMapper.CompletionFieldType fieldType = suggestionContext.getFieldType();
- if (fieldType == null) {
- throw new IllegalArgumentException("field [" + suggestionContext.getField() + "] is not a completion field");
- }
- CompletionSuggestion completionSuggestion = new CompletionSuggestion(name, suggestionContext.getSize());
- spare.copyUTF8Bytes(suggestionContext.getText());
- CompletionSuggestion.Entry completionSuggestEntry = new CompletionSuggestion.Entry(new Text(spare.toString()), 0, spare.length());
- completionSuggestion.addTerm(completionSuggestEntry);
- TopSuggestDocsCollector collector = new TopDocumentsCollector(suggestionContext.getSize());
- suggest(searcher, suggestionContext.toQuery(), collector);
- int numResult = 0;
- List<LeafReaderContext> leaves = searcher.getIndexReader().leaves();
- for (TopSuggestDocs.SuggestScoreDoc suggestScoreDoc : collector.get().scoreLookupDocs()) {
- TopDocumentsCollector.SuggestDoc suggestDoc = (TopDocumentsCollector.SuggestDoc) suggestScoreDoc;
- // collect contexts
- Map<String, Set<CharSequence>> contexts = Collections.emptyMap();
- if (fieldType.hasContextMappings() && suggestDoc.getContexts().isEmpty() == false) {
- contexts = fieldType.getContextMappings().getNamedContexts(suggestDoc.getContexts());
- }
- // collect payloads
- final Map<String, List<Object>> payload = new HashMap<>(0);
- List<String> payloadFields = suggestionContext.getPayloadFields();
- if (payloadFields.isEmpty() == false) {
- final int readerIndex = ReaderUtil.subIndex(suggestDoc.doc, leaves);
- final LeafReaderContext subReaderContext = leaves.get(readerIndex);
- final int subDocId = suggestDoc.doc - subReaderContext.docBase;
- for (String field : payloadFields) {
- MapperService mapperService = suggestionContext.getShardContext().getMapperService();
- MappedFieldType payloadFieldType = mapperService.fullName(field);
- if (payloadFieldType != null) {
- QueryShardContext shardContext = suggestionContext.getShardContext();
- final AtomicFieldData data = shardContext.getForField(payloadFieldType)
+ if (suggestionContext.getFieldType() != null) {
+ final CompletionFieldMapper.CompletionFieldType fieldType = suggestionContext.getFieldType();
+ CompletionSuggestion completionSuggestion = new CompletionSuggestion(name, suggestionContext.getSize());
+ spare.copyUTF8Bytes(suggestionContext.getText());
+ CompletionSuggestion.Entry completionSuggestEntry = new CompletionSuggestion.Entry(
+ new Text(spare.toString()), 0, spare.length());
+ completionSuggestion.addTerm(completionSuggestEntry);
+ TopSuggestDocsCollector collector = new TopDocumentsCollector(suggestionContext.getSize());
+ suggest(searcher, suggestionContext.toQuery(), collector);
+ int numResult = 0;
+ List<LeafReaderContext> leaves = searcher.getIndexReader().leaves();
+ for (TopSuggestDocs.SuggestScoreDoc suggestScoreDoc : collector.get().scoreLookupDocs()) {
+ TopDocumentsCollector.SuggestDoc suggestDoc = (TopDocumentsCollector.SuggestDoc) suggestScoreDoc;
+ // collect contexts
+ Map<String, Set<CharSequence>> contexts = Collections.emptyMap();
+ if (fieldType.hasContextMappings() && suggestDoc.getContexts().isEmpty() == false) {
+ contexts = fieldType.getContextMappings().getNamedContexts(suggestDoc.getContexts());
+ }
+ // collect payloads
+ final Map<String, List<Object>> payload = new HashMap<>(0);
+ List<String> payloadFields = suggestionContext.getPayloadFields();
+ if (payloadFields.isEmpty() == false) {
+ final int readerIndex = ReaderUtil.subIndex(suggestDoc.doc, leaves);
+ final LeafReaderContext subReaderContext = leaves.get(readerIndex);
+ final int subDocId = suggestDoc.doc - subReaderContext.docBase;
+ for (String field : payloadFields) {
+ MapperService mapperService = suggestionContext.getShardContext().getMapperService();
+ MappedFieldType payloadFieldType = mapperService.fullName(field);
+ if (payloadFieldType != null) {
+ QueryShardContext shardContext = suggestionContext.getShardContext();
+ final AtomicFieldData data = shardContext.getForField(payloadFieldType)
.load(subReaderContext);
- final ScriptDocValues scriptValues = data.getScriptValues();
- scriptValues.setNextDocId(subDocId);
- payload.put(field, new ArrayList<>(scriptValues.getValues()));
- } else {
- throw new IllegalArgumentException("payload field [" + field + "] does not exist");
+ final ScriptDocValues scriptValues = data.getScriptValues();
+ scriptValues.setNextDocId(subDocId);
+ payload.put(field, new ArrayList<>(scriptValues.getValues()));
+ } else {
+ throw new IllegalArgumentException("payload field [" + field + "] does not exist");
+ }
}
}
- }
- if (numResult++ < suggestionContext.getSize()) {
- CompletionSuggestion.Entry.Option option = new CompletionSuggestion.Entry.Option(
+ if (numResult++ < suggestionContext.getSize()) {
+ CompletionSuggestion.Entry.Option option = new CompletionSuggestion.Entry.Option(
new Text(suggestDoc.key.toString()), suggestDoc.score, contexts, payload);
- completionSuggestEntry.addOption(option);
- } else {
- break;
+ completionSuggestEntry.addOption(option);
+ } else {
+ break;
+ }
}
+ return completionSuggestion;
+ } else if (suggestionContext.getFieldType2x() != null) {
+ final IndexReader indexReader = searcher.getIndexReader();
+ org.elasticsearch.search.suggest.completion2x.CompletionSuggestion completionSuggestion =
+ new org.elasticsearch.search.suggest.completion2x.CompletionSuggestion(name, suggestionContext.getSize());
+ spare.copyUTF8Bytes(suggestionContext.getText());
+
+ org.elasticsearch.search.suggest.completion2x.CompletionSuggestion.Entry completionSuggestEntry =
+ new org.elasticsearch.search.suggest.completion2x.CompletionSuggestion.Entry(new Text(spare.toString()), 0, spare.length());
+ completionSuggestion.addTerm(completionSuggestEntry);
+
+ String fieldName = suggestionContext.getField();
+ Map<String, org.elasticsearch.search.suggest.completion2x.CompletionSuggestion.Entry.Option> results =
+ new HashMap<>(indexReader.leaves().size() * suggestionContext.getSize());
+ for (LeafReaderContext atomicReaderContext : indexReader.leaves()) {
+ LeafReader atomicReader = atomicReaderContext.reader();
+ Terms terms = atomicReader.fields().terms(fieldName);
+ if (terms instanceof Completion090PostingsFormat.CompletionTerms) {
+ final Completion090PostingsFormat.CompletionTerms lookupTerms = (Completion090PostingsFormat.CompletionTerms) terms;
+ final Lookup lookup = lookupTerms.getLookup(suggestionContext.getFieldType2x(), suggestionContext);
+ if (lookup == null) {
+ // we don't have a lookup for this segment.. this might be possible if a merge dropped all
+ // docs from the segment that had a value in this segment.
+ continue;
+ }
+ List<Lookup.LookupResult> lookupResults = lookup.lookup(spare.get(), false, suggestionContext.getSize());
+ for (Lookup.LookupResult res : lookupResults) {
+
+ final String key = res.key.toString();
+ final float score = res.value;
+ final org.elasticsearch.search.suggest.completion2x.CompletionSuggestion.Entry.Option value = results.get(key);
+ if (value == null) {
+ final org.elasticsearch.search.suggest.completion2x.CompletionSuggestion.Entry.Option option =
+ new org.elasticsearch.search.suggest.completion2x.CompletionSuggestion.Entry.Option(new Text(key), score,
+ res.payload == null ? null : new BytesArray(res.payload));
+ results.put(key, option);
+ } else if (value.getScore() < score) {
+ value.setScore(score);
+ value.setPayload(res.payload == null ? null : new BytesArray(res.payload));
+ }
+ }
+ }
+ }
+ final List<org.elasticsearch.search.suggest.completion2x.CompletionSuggestion.Entry.Option> options =
+ new ArrayList<>(results.values());
+ CollectionUtil.introSort(options, scoreComparator);
+
+ int optionCount = Math.min(suggestionContext.getSize(), options.size());
+ for (int i = 0; i < optionCount; i++) {
+ completionSuggestEntry.addOption(options.get(i));
+ }
+
+ return completionSuggestion;
+ }
+ return null;
+ }
+
+ private static final ScoreComparator scoreComparator = new ScoreComparator();
+ public static class ScoreComparator implements
+ Comparator<org.elasticsearch.search.suggest.completion2x.CompletionSuggestion.Entry.Option> {
+ @Override
+ public int compare(org.elasticsearch.search.suggest.completion2x.CompletionSuggestion.Entry.Option o1,
+ org.elasticsearch.search.suggest.completion2x.CompletionSuggestion.Entry.Option o2) {
+ return Float.compare(o2.getScore(), o1.getScore());
}
- return completionSuggestion;
}
private static void suggest(IndexSearcher searcher, CompletionQuery query, TopSuggestDocsCollector collector) throws IOException {
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java
index 2d9307a882..7810d03004 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java
@@ -34,6 +34,7 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.core.CompletionFieldMapper;
+import org.elasticsearch.index.mapper.core.CompletionFieldMapper2x;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.search.suggest.SuggestUtils;
@@ -41,6 +42,9 @@ import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContext;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
import org.elasticsearch.search.suggest.completion.context.ContextMappings;
+import org.elasticsearch.search.suggest.completion2x.context.CategoryContextMapping;
+import org.elasticsearch.search.suggest.completion2x.context.ContextMapping.ContextQuery;
+import org.elasticsearch.search.suggest.completion2x.context.GeolocationContextMapping;
import java.io.IOException;
import java.util.ArrayList;
@@ -219,13 +223,116 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
contentBuilder.endArray();
}
contentBuilder.endObject();
- contextBytes = contentBuilder.bytes();
- return this;
+ return contexts(contentBuilder);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
+ private CompletionSuggestionBuilder contexts(XContentBuilder contextBuilder) {
+ contextBytes = contextBuilder.bytes();
+ return this;
+ }
+
+ public CompletionSuggestionBuilder contexts(Contexts2x contexts2x) {
+ Objects.requireNonNull(contexts2x, "contexts must not be null");
+ try {
+ XContentBuilder contentBuilder = XContentFactory.jsonBuilder();
+ contentBuilder.startObject();
+ for (ContextQuery contextQuery : contexts2x.contextQueries) {
+ contextQuery.toXContent(contentBuilder, EMPTY_PARAMS);
+ }
+ contentBuilder.endObject();
+ return contexts(contentBuilder);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ // for 2.x context support
+ public static class Contexts2x {
+ private List<ContextQuery> contextQueries = new ArrayList<>();
+
+ @SuppressWarnings("unchecked")
+ private Contexts2x addContextQuery(ContextQuery ctx) {
+ this.contextQueries.add(ctx);
+ return this;
+ }
+
+ /**
+ * Setup a Geolocation for suggestions. See {@link GeolocationContextMapping}.
+ * @param lat Latitude of the location
+ * @param lon Longitude of the Location
+ * @return this
+ */
+ @Deprecated
+ public Contexts2x addGeoLocation(String name, double lat, double lon, int ... precisions) {
+ return addContextQuery(GeolocationContextMapping.query(name, lat, lon, precisions));
+ }
+
+ /**
+ * Setup a Geolocation for suggestions. See {@link GeolocationContextMapping}.
+ * @param lat Latitude of the location
+ * @param lon Longitude of the Location
+ * @param precisions precisions as string var-args
+ * @return this
+ */
+ @Deprecated
+ public Contexts2x addGeoLocationWithPrecision(String name, double lat, double lon, String ... precisions) {
+ return addContextQuery(GeolocationContextMapping.query(name, lat, lon, precisions));
+ }
+
+ /**
+ * Setup a Geolocation for suggestions. See {@link GeolocationContextMapping}.
+ * @param geohash Geohash of the location
+ * @return this
+ */
+ @Deprecated
+ public Contexts2x addGeoLocation(String name, String geohash) {
+ return addContextQuery(GeolocationContextMapping.query(name, geohash));
+ }
+
+ /**
+ * Setup a Category for suggestions. See {@link CategoryContextMapping}.
+ * @param categories name of the category
+ * @return this
+ */
+ @Deprecated
+ public Contexts2x addCategory(String name, CharSequence...categories) {
+ return addContextQuery(CategoryContextMapping.query(name, categories));
+ }
+
+ /**
+ * Setup a Category for suggestions. See {@link CategoryContextMapping}.
+ * @param categories name of the category
+ * @return this
+ */
+ @Deprecated
+ public Contexts2x addCategory(String name, Iterable<? extends CharSequence> categories) {
+ return addContextQuery(CategoryContextMapping.query(name, categories));
+ }
+
+ /**
+ * Setup a Context Field for suggestions. See {@link CategoryContextMapping}.
+ * @param fieldvalues name of the category
+ * @return this
+ */
+ @Deprecated
+ public Contexts2x addContextField(String name, CharSequence...fieldvalues) {
+ return addContextQuery(CategoryContextMapping.query(name, fieldvalues));
+ }
+
+ /**
+ * Setup a Context Field for suggestions. See {@link CategoryContextMapping}.
+ * @param fieldvalues name of the category
+ * @return this
+ */
+ @Deprecated
+ public Contexts2x addContextField(String name, Iterable<? extends CharSequence> fieldvalues) {
+ return addContextQuery(CategoryContextMapping.query(name, fieldvalues));
+ }
+ }
+
private static class InnerBuilder extends CompletionSuggestionBuilder {
private String field;
@@ -285,7 +392,12 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
suggestionContext.setFuzzyOptions(fuzzyOptions);
suggestionContext.setRegexOptions(regexOptions);
MappedFieldType mappedFieldType = mapperService.fullName(suggestionContext.getField());
- if (mappedFieldType != null && mappedFieldType instanceof CompletionFieldMapper.CompletionFieldType) {
+ if (mappedFieldType == null ||
+ (mappedFieldType instanceof CompletionFieldMapper.CompletionFieldType == false
+ && mappedFieldType instanceof CompletionFieldMapper2x.CompletionFieldType == false)) {
+ throw new IllegalArgumentException("Field [" + suggestionContext.getField() + "] is not a completion suggest field");
+ }
+ if (mappedFieldType instanceof CompletionFieldMapper.CompletionFieldType) {
CompletionFieldMapper.CompletionFieldType type = (CompletionFieldMapper.CompletionFieldType) mappedFieldType;
suggestionContext.setFieldType(type);
if (type.hasContextMappings() && contextBytes != null) {
@@ -310,9 +422,23 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
} else if (contextBytes != null) {
throw new IllegalArgumentException("suggester [" + type.name() + "] doesn't expect any context");
}
- } else {
- throw new IllegalArgumentException("Field [" + suggestionContext.getField() + "] is not a completion suggest field");
+ } else if (mappedFieldType instanceof CompletionFieldMapper2x.CompletionFieldType) {
+ CompletionFieldMapper2x.CompletionFieldType type = ((CompletionFieldMapper2x.CompletionFieldType) mappedFieldType);
+ suggestionContext.setFieldType2x(type);
+ if (type.requiresContext()) {
+ if (contextBytes != null) {
+ try (XContentParser contextParser = XContentFactory.xContent(contextBytes).createParser(contextBytes)) {
+ contextParser.nextToken();
+ suggestionContext.setContextQueries(ContextQuery.parseQueries(type.getContextMapping(), contextParser));
+ }
+ } else {
+ throw new IllegalArgumentException("suggester [completion] requires context to be setup");
+ }
+ } else if (contextBytes != null) {
+ throw new IllegalArgumentException("suggester [completion] doesn't expect any context");
+ }
}
+ assert suggestionContext.getFieldType() != null || suggestionContext.getFieldType2x() != null : "no completion field type set";
return suggestionContext;
}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionContext.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionContext.java
index 1941bc9fb8..268e0553ff 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionContext.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionContext.java
@@ -21,10 +21,12 @@ package org.elasticsearch.search.suggest.completion;
import org.apache.lucene.search.suggest.document.CompletionQuery;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.mapper.core.CompletionFieldMapper;
+import org.elasticsearch.index.mapper.core.CompletionFieldMapper2x;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.search.suggest.SuggestionSearchContext;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
import org.elasticsearch.search.suggest.completion.context.ContextMappings;
+import org.elasticsearch.search.suggest.completion2x.context.ContextMapping.ContextQuery;
import java.util.Collections;
import java.util.List;
@@ -44,11 +46,17 @@ public class CompletionSuggestionContext extends SuggestionSearchContext.Suggest
private RegexOptions regexOptions;
private Map<String, List<ContextMapping.InternalQueryContext>> queryContexts = Collections.emptyMap();
private List<String> payloadFields = Collections.emptyList();
+ private CompletionFieldMapper2x.CompletionFieldType fieldType2x;
+ private List<ContextQuery> contextQueries;
CompletionFieldMapper.CompletionFieldType getFieldType() {
return this.fieldType;
}
+ CompletionFieldMapper2x.CompletionFieldType getFieldType2x() {
+ return this.fieldType2x;
+ }
+
void setFieldType(CompletionFieldMapper.CompletionFieldType fieldType) {
this.fieldType = fieldType;
}
@@ -116,4 +124,16 @@ public class CompletionSuggestionContext extends SuggestionSearchContext.Suggest
}
return query;
}
+
+ public void setFieldType2x(CompletionFieldMapper2x.CompletionFieldType type) {
+ this.fieldType2x = type;
+ }
+
+ public void setContextQueries(List<ContextQuery> contextQueries) {
+ this.contextQueries = contextQueries;
+ }
+
+ public List<ContextQuery> getContextQueries() {
+ return contextQueries;
+ }
}