From d722c47224f645f9cb1ec174400ac29675671a3a Mon Sep 17 00:00:00 2001 From: Alex Ksikes Date: Thu, 10 Sep 2015 19:03:50 +0200 Subject: [PATCH 01/10] Refactors MoreLikeThisQueryBuilder and Parser Relates to #10217 This PR is against the query-refactoring branch. Closes #13486 --- .../index/query/MoreLikeThisQueryBuilder.java | 404 +++++++++++++++--- .../index/query/MoreLikeThisQueryParser.java | 285 +++--------- .../index/query/QueryShardContext.java | 11 +- .../query/MoreLikeThisQueryBuilderTests.java | 61 +++ .../query/SimpleIndexQueryParserTests.java | 70 +-- 5 files changed, 465 insertions(+), 366 deletions(-) create mode 100644 core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java index 52b0e363fa9b8..6d0b30b9eb842 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java @@ -19,25 +19,39 @@ package org.elasticsearch.index.query; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.index.Fields; +import org.apache.lucene.queries.TermsQuery; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.util.BytesRef; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ExceptionsHelper; -import org.elasticsearch.action.termvectors.TermVectorsRequest; +import org.elasticsearch.action.termvectors.*; +import org.elasticsearch.client.Client; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.lucene.search.MoreLikeThisQuery; +import org.elasticsearch.common.lucene.search.XMoreLikeThis; import org.elasticsearch.common.lucene.uid.Versions; -import org.elasticsearch.common.xcontent.ToXContent; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.common.xcontent.*; import org.elasticsearch.index.VersionType; +import org.elasticsearch.index.analysis.Analysis; +import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.index.mapper.internal.UidFieldMapper; +import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; import java.util.*; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.index.mapper.Uid.createUidAsBytes; /** * A more like this query that finds documents that are "like" the provided set of document(s). @@ -46,6 +60,46 @@ */ public class MoreLikeThisQueryBuilder extends AbstractQueryBuilder { + public static final String NAME = "mlt"; + + public static final int DEFAULT_MAX_QUERY_TERMS = XMoreLikeThis.DEFAULT_MAX_QUERY_TERMS; + public static final int DEFAULT_MIN_TERM_FREQ = XMoreLikeThis.DEFAULT_MIN_TERM_FREQ; + public static final int DEFAULT_MIN_DOC_FREQ = XMoreLikeThis.DEFAULT_MIN_DOC_FREQ; + public static final int DEFAULT_MAX_DOC_FREQ = XMoreLikeThis.DEFAULT_MAX_DOC_FREQ; + public static final int DEFAULT_MIN_WORD_LENGTH = XMoreLikeThis.DEFAULT_MIN_WORD_LENGTH; + public static final int DEFAULT_MAX_WORD_LENGTH = XMoreLikeThis.DEFAULT_MAX_WORD_LENGTH; + public static final String DEFAULT_MINIMUM_SHOULD_MATCH = MoreLikeThisQuery.DEFAULT_MINIMUM_SHOULD_MATCH; + public static final int DEFAULT_BOOST_TERMS = 0; // no boost terms + public static final boolean DEFAULT_INCLUDE = false; + public static final boolean DEFAULT_FAIL_ON_UNSUPPORTED_FIELDS = true; + + // document inputs + private final List fields; + private List likeTexts = new ArrayList<>(); + private List unlikeTexts = new ArrayList<>(); + private List likeItems = new ArrayList<>(); + private List unlikeItems = new ArrayList<>(); + + // term selection parameters + private int maxQueryTerms = DEFAULT_MAX_QUERY_TERMS; + private int minTermFreq = DEFAULT_MIN_TERM_FREQ; + private int minDocFreq = DEFAULT_MIN_DOC_FREQ; + private int maxDocFreq = DEFAULT_MAX_DOC_FREQ; + private int minWordLength = DEFAULT_MIN_WORD_LENGTH; + private int maxWordLength = DEFAULT_MAX_WORD_LENGTH; + private Set stopWords; + private String analyzer; + + // query formation parameters + private String minimumShouldMatch = DEFAULT_MINIMUM_SHOULD_MATCH; + private float boostTerms = DEFAULT_BOOST_TERMS; + private boolean include = DEFAULT_INCLUDE; + + // other parameters + private Boolean failOnUnsupportedField = DEFAULT_FAIL_ON_UNSUPPORTED_FIELDS; + + static final MoreLikeThisQueryBuilder PROTOTYPE = new MoreLikeThisQueryBuilder(); + /** * A single item to be used for a {@link MoreLikeThisQueryBuilder}. */ @@ -349,36 +403,6 @@ public boolean equals(Object o) { } } - public static final String NAME = "mlt"; - - // document inputs - private List likeTexts = new ArrayList<>(); - private List unlikeTexts = new ArrayList<>(); - private List likeItems = new ArrayList<>(); - private List unlikeItems = new ArrayList<>(); - private final String[] fields; - - // term selection parameters - private int maxQueryTerms = -1; - private int minTermFreq = -1; - private int minDocFreq = -1; - private int maxDocFreq = -1; - private int minWordLength = -1; - private int maxWordLength = -1; - private String[] stopWords = null; - private String analyzer; - - // query formation parameters - private String minimumShouldMatch = null; - private float boostTerms = -1; - private Boolean include = null; - - // other parameters - private Boolean failOnUnsupportedField; - - static final MoreLikeThisQueryBuilder PROTOTYPE = new MoreLikeThisQueryBuilder(); - - /** * Constructs a new more like this query which uses the "_all" field. */ @@ -392,6 +416,15 @@ public MoreLikeThisQueryBuilder() { * @param fields the field names that will be used when generating the 'More Like This' query. */ public MoreLikeThisQueryBuilder(String... fields) { + this(Arrays.asList(fields)); + } + + /** + * Sets the field names that will be used when generating the 'More Like This' query. + * + * @param fields the field names that will be used when generating the 'More Like This' query. + */ + public MoreLikeThisQueryBuilder(List fields) { this.fields = fields; } @@ -517,6 +550,29 @@ public MoreLikeThisQueryBuilder maxWordLength(int maxWordLength) { return this; } + /** + * Set the set of stopwords. + *

+ *

Any word in this set is considered "uninteresting" and ignored. Even if your Analyzer allows stopwords, you + * might want to tell the MoreLikeThis code to ignore them, as for the purposes of document similarity it seems + * reasonable to assume that "a stop word is never interesting". + */ + public MoreLikeThisQueryBuilder stopWords(String... stopWords) { + return stopWords(new HashSet<>(Arrays.asList(stopWords))); + } + + /** + * Set the set of stopwords. + *

+ *

Any word in this set is considered "uninteresting" and ignored. Even if your Analyzer allows stopwords, you + * might want to tell the MoreLikeThis code to ignore them, as for the purposes of document similarity it seems + * reasonable to assume that "a stop word is never interesting". + */ + public MoreLikeThisQueryBuilder stopWords(Set stopWords) { + this.stopWords = stopWords; + return this; + } + /** * The analyzer that will be used to analyze the text. Defaults to the analyzer associated with the fied. */ @@ -537,7 +593,7 @@ public MoreLikeThisQueryBuilder minimumShouldMatch(String minimumShouldMatch) { } /** - * Sets the boost factor to use when boosting terms. Defaults to 1. + * Sets the boost factor to use when boosting terms. Defaults to 0 (deactivated). */ public MoreLikeThisQueryBuilder boostTerms(float boostTerms) { this.boostTerms = boostTerms; @@ -624,39 +680,21 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep if (!unlikeTexts.isEmpty() || !unlikeItems.isEmpty()) { buildLikeField(builder, MoreLikeThisQueryParser.Field.UNLIKE.getPreferredName(), unlikeTexts, unlikeItems); } - if (maxQueryTerms != -1) { - builder.field(MoreLikeThisQueryParser.Field.MAX_QUERY_TERMS.getPreferredName(), maxQueryTerms); - } - if (minTermFreq != -1) { - builder.field(MoreLikeThisQueryParser.Field.MIN_TERM_FREQ.getPreferredName(), minTermFreq); - } - if (minDocFreq != -1) { - builder.field(MoreLikeThisQueryParser.Field.MIN_DOC_FREQ.getPreferredName(), minDocFreq); - } - if (maxDocFreq != -1) { - builder.field(MoreLikeThisQueryParser.Field.MAX_DOC_FREQ.getPreferredName(), maxDocFreq); - } - if (minWordLength != -1) { - builder.field(MoreLikeThisQueryParser.Field.MIN_WORD_LENGTH.getPreferredName(), minWordLength); - } - if (maxWordLength != -1) { - builder.field(MoreLikeThisQueryParser.Field.MAX_WORD_LENGTH.getPreferredName(), maxWordLength); - } - if (stopWords != null && stopWords.length > 0) { + builder.field(MoreLikeThisQueryParser.Field.MAX_QUERY_TERMS.getPreferredName(), maxQueryTerms); + builder.field(MoreLikeThisQueryParser.Field.MIN_TERM_FREQ.getPreferredName(), minTermFreq); + builder.field(MoreLikeThisQueryParser.Field.MIN_DOC_FREQ.getPreferredName(), minDocFreq); + builder.field(MoreLikeThisQueryParser.Field.MAX_DOC_FREQ.getPreferredName(), maxDocFreq); + builder.field(MoreLikeThisQueryParser.Field.MIN_WORD_LENGTH.getPreferredName(), minWordLength); + builder.field(MoreLikeThisQueryParser.Field.MAX_WORD_LENGTH.getPreferredName(), maxWordLength); + if (stopWords != null && stopWords.size() > 0) { builder.field(MoreLikeThisQueryParser.Field.STOP_WORDS.getPreferredName(), stopWords); } if (analyzer != null) { builder.field(MoreLikeThisQueryParser.Field.ANALYZER.getPreferredName(), analyzer); } - if (minimumShouldMatch != null) { - builder.field(MoreLikeThisQueryParser.Field.MINIMUM_SHOULD_MATCH.getPreferredName(), minimumShouldMatch); - } - if (boostTerms != -1) { - builder.field(MoreLikeThisQueryParser.Field.BOOST_TERMS.getPreferredName(), boostTerms); - } - if (include != null) { - builder.field(MoreLikeThisQueryParser.Field.INCLUDE.getPreferredName(), include); - } + builder.field(MoreLikeThisQueryParser.Field.MINIMUM_SHOULD_MATCH.getPreferredName(), minimumShouldMatch); + builder.field(MoreLikeThisQueryParser.Field.BOOST_TERMS.getPreferredName(), boostTerms); + builder.field(MoreLikeThisQueryParser.Field.INCLUDE.getPreferredName(), include); if (failOnUnsupportedField != null) { builder.field(MoreLikeThisQueryParser.Field.FAIL_ON_UNSUPPORTED_FIELD.getPreferredName(), failOnUnsupportedField); } @@ -675,6 +713,242 @@ private static void buildLikeField(XContentBuilder builder, String fieldName, Li builder.endArray(); } + @Override + protected void setFinalBoost(Query query) { + super.setFinalBoost(query); + } + + @Override + protected Query doToQuery(QueryShardContext context) throws IOException { + MoreLikeThisQuery mltQuery = new MoreLikeThisQuery(); + + // set similarity + mltQuery.setSimilarity(context.searchSimilarity()); + + // set query parameters + mltQuery.setMaxQueryTerms(maxQueryTerms); + mltQuery.setMinTermFrequency(minTermFreq); + mltQuery.setMinDocFreq(minDocFreq); + mltQuery.setMaxDocFreq(maxDocFreq); + mltQuery.setMinWordLen(minWordLength); + mltQuery.setMaxWordLen(maxWordLength); + mltQuery.setMinimumShouldMatch(minimumShouldMatch); + mltQuery.setStopWords(stopWords); + + // sets boost terms + if (boostTerms != 0) { + mltQuery.setBoostTerms(true); + mltQuery.setBoostTermsFactor(boostTerms); + } + + // set analyzer + Analyzer analyzerObj = context.analysisService().analyzer(analyzer); + if (analyzerObj == null) { + analyzerObj = context.mapperService().searchAnalyzer(); + } + mltQuery.setAnalyzer(analyzerObj); + + // set like text fields + boolean useDefaultField = (fields == null); + List moreLikeFields = new ArrayList<>(); + if (useDefaultField) { + moreLikeFields = Collections.singletonList(context.defaultField()); + } else { + for (String field : fields) { + MappedFieldType fieldType = context.fieldMapper(field); + moreLikeFields.add(fieldType == null ? field : fieldType.names().indexName()); + } + } + + // possibly remove unsupported fields + removeUnsupportedFields(moreLikeFields, analyzerObj, failOnUnsupportedField); + if (moreLikeFields.isEmpty()) { + return null; + } + mltQuery.setMoreLikeFields(moreLikeFields.toArray(Strings.EMPTY_ARRAY)); + + // handle like texts + if (!likeTexts.isEmpty()) { + mltQuery.setLikeText(likeTexts); + } + if (!unlikeTexts.isEmpty()) { + mltQuery.setUnlikeText(unlikeTexts); + } + + // handle items + if (!likeItems.isEmpty()) { + return handleItems(context, mltQuery, likeItems, unlikeItems, include, moreLikeFields, useDefaultField); + } else { + return mltQuery; + } + } + + private static List removeUnsupportedFields(List moreLikeFields, Analyzer analyzer, boolean failOnUnsupportedField) throws IOException { + for (Iterator it = moreLikeFields.iterator(); it.hasNext(); ) { + final String fieldName = it.next(); + if (!Analysis.generatesCharacterTokenStream(analyzer, fieldName)) { + if (failOnUnsupportedField) { + throw new IllegalArgumentException("more_like_this doesn't support binary/numeric fields: [" + fieldName + "]"); + } else { + it.remove(); + } + } + } + return moreLikeFields; + } + + private Query handleItems(QueryShardContext context, MoreLikeThisQuery mltQuery, List likeItems, List unlikeItems, + boolean include, List moreLikeFields, boolean useDefaultField) throws IOException { + // set default index, type and fields if not specified + for (Item item : likeItems) { + setDefaultIndexTypeFields(context, item, moreLikeFields, useDefaultField); + } + for (Item item : unlikeItems) { + setDefaultIndexTypeFields(context, item, moreLikeFields, useDefaultField); + } + + // fetching the items with multi-termvectors API + MultiTermVectorsResponse responses = fetchResponse(context.getClient(), likeItems, unlikeItems, SearchContext.current()); + + // getting the Fields for liked items + mltQuery.setLikeText(getFieldsFor(responses, likeItems)); + + // getting the Fields for unliked items + if (!unlikeItems.isEmpty()) { + org.apache.lucene.index.Fields[] unlikeFields = getFieldsFor(responses, unlikeItems); + if (unlikeFields.length > 0) { + mltQuery.setUnlikeText(unlikeFields); + } + } + + BooleanQuery boolQuery = new BooleanQuery(); + boolQuery.add(mltQuery, BooleanClause.Occur.SHOULD); + + // exclude the items from the search + if (!include) { + handleExclude(boolQuery, likeItems); + } + return boolQuery; + } + + private static void setDefaultIndexTypeFields(QueryShardContext context, Item item, List moreLikeFields, + boolean useDefaultField) { + if (item.index() == null) { + item.index(context.index().name()); + } + if (item.type() == null) { + if (context.queryTypes().size() > 1) { + throw new QueryShardException(context, + "ambiguous type for item with id: " + item.id() + " and index: " + item.index()); + } else { + item.type(context.queryTypes().iterator().next()); + } + } + // default fields if not present but don't override for artificial docs + if ((item.fields() == null || item.fields().length == 0) && item.doc() == null) { + if (useDefaultField) { + item.fields("*"); + } else { + item.fields(moreLikeFields.toArray(new String[moreLikeFields.size()])); + } + } + } + + private MultiTermVectorsResponse fetchResponse(Client client, List likeItems, @Nullable List unlikeItems, + SearchContext searchContext) throws IOException { + MultiTermVectorsRequest request = new MultiTermVectorsRequest(); + for (Item item : likeItems) { + request.add(item.toTermVectorsRequest()); + } + if (unlikeItems != null) { + for (Item item : unlikeItems) { + request.add(item.toTermVectorsRequest()); + } + } + request.copyContextAndHeadersFrom(searchContext); + return client.multiTermVectors(request).actionGet(); + } + + private static Fields[] getFieldsFor(MultiTermVectorsResponse responses, List items) throws IOException { + List likeFields = new ArrayList<>(); + + Set selectedItems = new HashSet<>(); + for (Item request : items) { + selectedItems.add(new Item(request.index(), request.type(), request.id())); + } + + for (MultiTermVectorsItemResponse response : responses) { + if (!hasResponseFromRequest(response, selectedItems)) { + continue; + } + if (response.isFailed()) { + continue; + } + TermVectorsResponse getResponse = response.getResponse(); + if (!getResponse.isExists()) { + continue; + } + likeFields.add(getResponse.getFields()); + } + return likeFields.toArray(Fields.EMPTY_ARRAY); + } + + private static boolean hasResponseFromRequest(MultiTermVectorsItemResponse response, Set selectedItems) { + return selectedItems.contains(new Item(response.getIndex(), response.getType(), response.getId())); + } + + private static void handleExclude(BooleanQuery boolQuery, List likeItems) { + // artificial docs get assigned a random id and should be disregarded + List uids = new ArrayList<>(); + for (Item item : likeItems) { + if (item.doc() != null) { + continue; + } + uids.add(createUidAsBytes(item.type(), item.id())); + } + if (!uids.isEmpty()) { + TermsQuery query = new TermsQuery(UidFieldMapper.NAME, uids.toArray(new BytesRef[0])); + boolQuery.add(query, BooleanClause.Occur.MUST_NOT); + } + } + + @Override + public QueryValidationException validate() { + QueryValidationException validationException = null; + if (likeTexts.isEmpty() && likeItems.isEmpty()) { + validationException = addValidationError("more_like_this requires 'like' to be specified.", validationException); + } + if (fields != null && fields.isEmpty()) { + validationException = addValidationError("more_like_this requires 'like' to be specified", validationException); + } + return validationException; + } + + @Override + protected MoreLikeThisQueryBuilder doReadFrom(StreamInput in) throws IOException { + return super.doReadFrom(in); + } + + @Override + protected void doWriteTo(StreamOutput out) throws IOException { + super.doWriteTo(out); + } + + @Override + protected boolean doEquals(MoreLikeThisQueryBuilder other) { + return super.doEquals(other); + } + + @Override + protected int doHashCode() { + return super.doHashCode(); + } + + @Override + public String getName() { + return super.getName(); + } + @Override public String getWriteableName() { return NAME; diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java index 07adeeceedbcb..ec503de686ea5 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java @@ -19,43 +19,22 @@ package org.elasticsearch.index.query; -import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.index.Fields; -import org.apache.lucene.queries.TermsQuery; -import org.apache.lucene.search.BooleanClause; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.Query; -import org.apache.lucene.util.BytesRef; -import org.elasticsearch.action.termvectors.*; -import org.elasticsearch.client.Client; -import org.elasticsearch.common.Nullable; +import com.google.common.collect.Sets; import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.lucene.search.MoreLikeThisQuery; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.analysis.Analysis; -import org.elasticsearch.index.mapper.MappedFieldType; -import org.elasticsearch.index.mapper.internal.UidFieldMapper; import org.elasticsearch.index.query.MoreLikeThisQueryBuilder.Item; -import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Set; -import static org.elasticsearch.index.mapper.Uid.createUidAsBytes; - /** * Parser for the The More Like This Query (MLT Query) which finds documents that are "like" a given set of documents. * * The documents are provided as a set of strings and/or a list of {@link Item}. */ -public class MoreLikeThisQueryParser extends BaseQueryParserTemp { +public class MoreLikeThisQueryParser extends BaseQueryParser { public interface Field { ParseField FIELDS = new ParseField("fields"); @@ -78,29 +57,44 @@ public interface Field { ParseField FAIL_ON_UNSUPPORTED_FIELD = new ParseField("fail_on_unsupported_field"); } + public MoreLikeThisQueryParser() { + + } + @Override public String[] names() { return new String[]{MoreLikeThisQueryBuilder.NAME, "more_like_this", "moreLikeThis"}; } @Override - public Query parse(QueryShardContext context) throws IOException, QueryParsingException { - QueryParseContext parseContext = context.parseContext(); + public MoreLikeThisQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, QueryParsingException { XContentParser parser = parseContext.parser(); - MoreLikeThisQuery mltQuery = new MoreLikeThisQuery(); - mltQuery.setSimilarity(context.searchSimilarity()); - + // document inputs + List moreLikeFields = null; List likeTexts = new ArrayList<>(); List unlikeTexts = new ArrayList<>(); List likeItems = new ArrayList<>(); List unlikeItems = new ArrayList<>(); - List moreLikeFields = null; - Analyzer analyzer = null; - boolean include = false; - - boolean failOnUnsupportedField = true; + // term selection parameters + int maxQueryTerms = MoreLikeThisQueryBuilder.DEFAULT_MAX_QUERY_TERMS; + int minTermFreq = MoreLikeThisQueryBuilder.DEFAULT_MIN_TERM_FREQ; + int minDocFreq = MoreLikeThisQueryBuilder.DEFAULT_MIN_DOC_FREQ; + int maxDocFreq = MoreLikeThisQueryBuilder.DEFAULT_MAX_DOC_FREQ; + int minWordLength = MoreLikeThisQueryBuilder.DEFAULT_MIN_WORD_LENGTH; + int maxWordLength = MoreLikeThisQueryBuilder.DEFAULT_MAX_WORD_LENGTH; + Set stopWords = null; + String analyzer = null; + + // query formation parameters + String minimumShouldMatch = MoreLikeThisQueryBuilder.DEFAULT_MINIMUM_SHOULD_MATCH; + float boostTerms = MoreLikeThisQueryBuilder.DEFAULT_BOOST_TERMS; + boolean include = MoreLikeThisQueryBuilder.DEFAULT_INCLUDE; + + // other parameters + boolean failOnUnsupportedField = MoreLikeThisQueryBuilder.DEFAULT_FAIL_ON_UNSUPPORTED_FIELDS; + float boost = AbstractQueryBuilder.DEFAULT_BOOST; String queryName = null; XContentParser.Token token; @@ -116,37 +110,29 @@ public Query parse(QueryShardContext context) throws IOException, QueryParsingEx } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.LIKE_TEXT)) { likeTexts.add(parser.text()); } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.MAX_QUERY_TERMS)) { - mltQuery.setMaxQueryTerms(parser.intValue()); + maxQueryTerms = parser.intValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.MIN_TERM_FREQ)) { - mltQuery.setMinTermFrequency(parser.intValue()); + minTermFreq =parser.intValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.MIN_DOC_FREQ)) { - mltQuery.setMinDocFreq(parser.intValue()); + minDocFreq = parser.intValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.MAX_DOC_FREQ)) { - mltQuery.setMaxDocFreq(parser.intValue()); + maxDocFreq = parser.intValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.MIN_WORD_LENGTH)) { - mltQuery.setMinWordLen(parser.intValue()); + minWordLength = parser.intValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.MAX_WORD_LENGTH)) { - mltQuery.setMaxWordLen(parser.intValue()); + maxWordLength = parser.intValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.ANALYZER)) { - analyzer = context.analysisService().analyzer(parser.text()); + analyzer = parser.text(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.MINIMUM_SHOULD_MATCH)) { - mltQuery.setMinimumShouldMatch(parser.text()); + minimumShouldMatch = parser.text(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.BOOST_TERMS)) { - float boostFactor = parser.floatValue(); - if (boostFactor != 0) { - mltQuery.setBoostTerms(true); - mltQuery.setBoostTermsFactor(boostFactor); - } - } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.MINIMUM_SHOULD_MATCH)) { - mltQuery.setMinimumShouldMatch(parser.text()); - } else if ("analyzer".equals(currentFieldName)) { - analyzer = context.analysisService().analyzer(parser.text()); + boostTerms = parser.floatValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.INCLUDE)) { include = parser.booleanValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.FAIL_ON_UNSUPPORTED_FIELD)) { failOnUnsupportedField = parser.booleanValue(); } else if ("boost".equals(currentFieldName)) { - mltQuery.setBoost(parser.floatValue()); + boost = parser.floatValue(); } else if ("_name".equals(currentFieldName)) { queryName = parser.text(); } else { @@ -154,11 +140,9 @@ public Query parse(QueryShardContext context) throws IOException, QueryParsingEx } } else if (token == XContentParser.Token.START_ARRAY) { if (parseContext.parseFieldMatcher().match(currentFieldName, Field.FIELDS)) { - moreLikeFields = new LinkedList<>(); + moreLikeFields = new ArrayList<>(); while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - String field = parser.text(); - MappedFieldType fieldType = context.fieldMapper(field); - moreLikeFields.add(fieldType == null ? field : fieldType.names().indexName()); + moreLikeFields.add(parser.text()); } } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.LIKE)) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { @@ -183,11 +167,10 @@ public Query parse(QueryShardContext context) throws IOException, QueryParsingEx likeItems.add(Item.parse(parser, parseContext.parseFieldMatcher(), new Item())); } } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.STOP_WORDS)) { - Set stopWords = new HashSet<>(); + stopWords = Sets.newHashSet(); while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { stopWords.add(parser.text()); } - mltQuery.setStopWords(stopWords); } else { throw new QueryParsingException(parseContext, "[mlt] query does not support [" + currentFieldName + "]"); } @@ -209,44 +192,25 @@ public Query parse(QueryShardContext context) throws IOException, QueryParsingEx throw new QueryParsingException(parseContext, "more_like_this requires 'fields' to be non-empty"); } - // set analyzer - if (analyzer == null) { - analyzer = context.mapperService().searchAnalyzer(); - } - mltQuery.setAnalyzer(analyzer); - - // set like text fields - boolean useDefaultField = (moreLikeFields == null); - if (useDefaultField) { - moreLikeFields = Collections.singletonList(context.defaultField()); - } - - // possibly remove unsupported fields - removeUnsupportedFields(moreLikeFields, analyzer, failOnUnsupportedField); - if (moreLikeFields.isEmpty()) { - return null; - } - mltQuery.setMoreLikeFields(moreLikeFields.toArray(Strings.EMPTY_ARRAY)); - - // support for named query - if (queryName != null) { - context.addNamedQuery(queryName, mltQuery); - } - - // handle like texts - if (!likeTexts.isEmpty()) { - mltQuery.setLikeText(likeTexts); - } - if (!unlikeTexts.isEmpty()) { - mltQuery.setUnlikeText(unlikeTexts); - } - - // handle items - if (!likeItems.isEmpty()) { - return handleItems(context, mltQuery, likeItems, unlikeItems, include, moreLikeFields, useDefaultField); - } else { - return mltQuery; - } + return new MoreLikeThisQueryBuilder(moreLikeFields) + .like(likeTexts.toArray(new String[likeTexts.size()])) + .unlike(unlikeTexts.toArray(new String[unlikeTexts.size()])) + .like(likeItems.toArray(new Item[likeItems.size()])) + .unlike(unlikeItems.toArray(new Item[unlikeItems.size()])) + .maxQueryTerms(maxQueryTerms) + .minTermFreq(minTermFreq) + .minDocFreq(minDocFreq) + .maxDocFreq(maxDocFreq) + .minWordLength(minWordLength) + .maxWordLength(maxWordLength) + .stopWords(stopWords) + .analyzer(analyzer) + .minimumShouldMatch(minimumShouldMatch) + .boostTerms(boostTerms) + .include(include) + .failOnUnsupportedField(failOnUnsupportedField) + .boost(boost) + .queryName(queryName); } private static void parseLikeField(QueryParseContext parseContext, List texts, List items) throws IOException { @@ -260,139 +224,8 @@ private static void parseLikeField(QueryParseContext parseContext, List } } - private static List removeUnsupportedFields(List moreLikeFields, Analyzer analyzer, boolean failOnUnsupportedField) throws IOException { - for (Iterator it = moreLikeFields.iterator(); it.hasNext(); ) { - final String fieldName = it.next(); - if (!Analysis.generatesCharacterTokenStream(analyzer, fieldName)) { - if (failOnUnsupportedField) { - throw new IllegalArgumentException("more_like_this doesn't support binary/numeric fields: [" + fieldName + "]"); - } else { - it.remove(); - } - } - } - return moreLikeFields; - } - - private Query handleItems(QueryShardContext context, MoreLikeThisQuery mltQuery, List likeItems, List unlikeItems, - boolean include, List moreLikeFields, boolean useDefaultField) throws IOException { - - QueryParseContext parseContext = context.parseContext(); - // set default index, type and fields if not specified - for (Item item : likeItems) { - setDefaultIndexTypeFields(parseContext, item, moreLikeFields, useDefaultField); - } - for (Item item : unlikeItems) { - setDefaultIndexTypeFields(parseContext, item, moreLikeFields, useDefaultField); - } - - // fetching the items with multi-termvectors API - MultiTermVectorsResponse responses = fetchResponse(context.getClient(), likeItems, unlikeItems, SearchContext.current()); - - // getting the Fields for liked items - mltQuery.setLikeText(getFieldsFor(responses, likeItems)); - - // getting the Fields for unliked items - if (!unlikeItems.isEmpty()) { - org.apache.lucene.index.Fields[] unlikeFields = getFieldsFor(responses, unlikeItems); - if (unlikeFields.length > 0) { - mltQuery.setUnlikeText(unlikeFields); - } - } - - BooleanQuery boolQuery = new BooleanQuery(); - boolQuery.add(mltQuery, BooleanClause.Occur.SHOULD); - - // exclude the items from the search - if (!include) { - handleExclude(boolQuery, likeItems); - } - return boolQuery; - } - - private static void setDefaultIndexTypeFields(QueryParseContext parseContext, Item item, List moreLikeFields, - boolean useDefaultField) { - if (item.index() == null) { - item.index(parseContext.index().name()); - } - if (item.type() == null) { - if (parseContext.shardContext().queryTypes().size() > 1) { - throw new QueryParsingException(parseContext, - "ambiguous type for item with id: " + item.id() + " and index: " + item.index()); - } else { - item.type(parseContext.shardContext().queryTypes().iterator().next()); - } - } - // default fields if not present but don't override for artificial docs - if ((item.fields() == null || item.fields().length == 0) && item.doc() == null) { - if (useDefaultField) { - item.fields("*"); - } else { - item.fields(moreLikeFields.toArray(new String[moreLikeFields.size()])); - } - } - } - - private static void handleExclude(BooleanQuery boolQuery, List likeItems) { - // artificial docs get assigned a random id and should be disregarded - List uids = new ArrayList<>(); - for (Item item : likeItems) { - if (item.doc() != null) { - continue; - } - uids.add(createUidAsBytes(item.type(), item.id())); - } - if (!uids.isEmpty()) { - TermsQuery query = new TermsQuery(UidFieldMapper.NAME, uids.toArray(new BytesRef[0])); - boolQuery.add(query, BooleanClause.Occur.MUST_NOT); - } - } - @Override public MoreLikeThisQueryBuilder getBuilderPrototype() { return MoreLikeThisQueryBuilder.PROTOTYPE; } - - private MultiTermVectorsResponse fetchResponse(Client client, List likeItems, @Nullable List unlikeItems, - SearchContext searchContext) throws IOException { - MultiTermVectorsRequest request = new MultiTermVectorsRequest(); - for (Item item : likeItems) { - request.add(item.toTermVectorsRequest()); - } - if (unlikeItems != null) { - for (Item item : unlikeItems) { - request.add(item.toTermVectorsRequest()); - } - } - request.copyContextAndHeadersFrom(searchContext); - return client.multiTermVectors(request).actionGet(); - } - - private static Fields[] getFieldsFor(MultiTermVectorsResponse responses, List items) throws IOException { - List likeFields = new ArrayList<>(); - - Set selectedItems = new HashSet<>(); - for (Item request : items) { - selectedItems.add(new Item(request.index(), request.type(), request.id())); - } - - for (MultiTermVectorsItemResponse response : responses) { - if (!hasResponseFromRequest(response, selectedItems)) { - continue; - } - if (response.isFailed()) { - continue; - } - TermVectorsResponse getResponse = response.getResponse(); - if (!getResponse.isExists()) { - continue; - } - likeFields.add(getResponse.getFields()); - } - return likeFields.toArray(Fields.EMPTY_ARRAY); - } - - private static boolean hasResponseFromRequest(MultiTermVectorsItemResponse response, Set selectedItems) { - return selectedItems.contains(new Item(response.getIndex(), response.getType(), response.getId())); - } } diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java b/core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java index 8ce547ed45da1..6dd2e499b0a31 100644 --- a/core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java +++ b/core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java @@ -20,7 +20,6 @@ package org.elasticsearch.index.query; import com.google.common.collect.ImmutableMap; - import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.queryparser.classic.MapperQueryParser; import org.apache.lucene.queryparser.classic.QueryParserSettings; @@ -33,21 +32,15 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.Index; import org.elasticsearch.index.analysis.AnalysisService; import org.elasticsearch.index.fielddata.IndexFieldData; -import org.elasticsearch.index.mapper.ContentPath; -import org.elasticsearch.index.mapper.MappedFieldType; -import org.elasticsearch.index.mapper.Mapper; -import org.elasticsearch.index.mapper.MapperBuilders; -import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.mapper.*; import org.elasticsearch.index.mapper.core.StringFieldMapper; import org.elasticsearch.index.mapper.object.ObjectMapper; import org.elasticsearch.index.query.support.NestedScope; -import org.elasticsearch.indices.cache.query.terms.TermsLookup; import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.ScriptService; @@ -56,11 +49,9 @@ import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.lookup.SearchLookup; -import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.Map; /** diff --git a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java new file mode 100644 index 0000000000000..81150d9d4ca37 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch 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.elasticsearch.index.query; + +import org.apache.lucene.search.Query; +import org.elasticsearch.action.termvectors.MultiTermVectorsRequest; +import org.elasticsearch.action.termvectors.MultiTermVectorsResponse; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +public class MoreLikeThisQueryBuilderTests extends AbstractQueryTestCase { + + private List randomItems; + + @Before + public void randomItems() { + + } + + + @Override + protected MoreLikeThisQueryBuilder doCreateTestQueryBuilder() { + return null; + } + + @Override + protected void doAssertLuceneQuery(MoreLikeThisQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException { + + } + + @Test + public void testValidate() { + + } + + @Override + protected MultiTermVectorsResponse executeMultiTermVectors(MultiTermVectorsRequest mtvRequest) { + throw new UnsupportedOperationException("this test can't handle MultiTermVector requests"); + } + +} diff --git a/core/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java b/core/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java index 747bc060bc67e..e69d1bbc03ea6 100644 --- a/core/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java @@ -20,41 +20,14 @@ package org.elasticsearch.index.query; import org.apache.lucene.analysis.core.WhitespaceAnalyzer; -import org.apache.lucene.index.Fields; -import org.apache.lucene.index.MultiFields; -import org.apache.lucene.index.Term; -import org.apache.lucene.index.Terms; -import org.apache.lucene.index.TermsEnum; +import org.apache.lucene.index.*; import org.apache.lucene.index.memory.MemoryIndex; import org.apache.lucene.queries.BoostingQuery; import org.apache.lucene.queries.ExtendedCommonTermsQuery; import org.apache.lucene.queries.TermsQuery; -import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.*; import org.apache.lucene.search.BooleanClause.Occur; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.BoostQuery; -import org.apache.lucene.search.ConstantScoreQuery; -import org.apache.lucene.search.DisjunctionMaxQuery; -import org.apache.lucene.search.FuzzyQuery; -import org.apache.lucene.search.MatchAllDocsQuery; -import org.apache.lucene.search.MultiTermQuery; -import org.apache.lucene.search.NumericRangeQuery; -import org.apache.lucene.search.PrefixQuery; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.QueryWrapperFilter; -import org.apache.lucene.search.RegexpQuery; -import org.apache.lucene.search.TermQuery; -import org.apache.lucene.search.TermRangeQuery; -import org.apache.lucene.search.WildcardQuery; -import org.apache.lucene.search.spans.FieldMaskingSpanQuery; -import org.apache.lucene.search.spans.SpanContainingQuery; -import org.apache.lucene.search.spans.SpanFirstQuery; -import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper; -import org.apache.lucene.search.spans.SpanNearQuery; -import org.apache.lucene.search.spans.SpanNotQuery; -import org.apache.lucene.search.spans.SpanOrQuery; -import org.apache.lucene.search.spans.SpanTermQuery; -import org.apache.lucene.search.spans.SpanWithinQuery; +import org.apache.lucene.search.spans.*; import org.apache.lucene.spatial.prefix.IntersectsPrefixTreeFilter; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefBuilder; @@ -80,7 +53,6 @@ import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.core.NumberFieldMapper; -import org.elasticsearch.index.query.MoreLikeThisQueryBuilder.Item; import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders; import org.elasticsearch.index.search.geo.GeoDistanceRangeQuery; import org.elasticsearch.index.search.geo.GeoPolygonQuery; @@ -99,43 +71,11 @@ import java.util.concurrent.ExecutionException; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.elasticsearch.index.query.QueryBuilders.boolQuery; -import static org.elasticsearch.index.query.QueryBuilders.boostingQuery; -import static org.elasticsearch.index.query.QueryBuilders.commonTermsQuery; -import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery; -import static org.elasticsearch.index.query.QueryBuilders.disMaxQuery; -import static org.elasticsearch.index.query.QueryBuilders.functionScoreQuery; -import static org.elasticsearch.index.query.QueryBuilders.fuzzyQuery; -import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; -import static org.elasticsearch.index.query.QueryBuilders.moreLikeThisQuery; -import static org.elasticsearch.index.query.QueryBuilders.multiMatchQuery; -import static org.elasticsearch.index.query.QueryBuilders.notQuery; -import static org.elasticsearch.index.query.QueryBuilders.prefixQuery; -import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery; -import static org.elasticsearch.index.query.QueryBuilders.rangeQuery; -import static org.elasticsearch.index.query.QueryBuilders.regexpQuery; -import static org.elasticsearch.index.query.QueryBuilders.spanContainingQuery; -import static org.elasticsearch.index.query.QueryBuilders.spanFirstQuery; -import static org.elasticsearch.index.query.QueryBuilders.spanNearQuery; -import static org.elasticsearch.index.query.QueryBuilders.spanNotQuery; -import static org.elasticsearch.index.query.QueryBuilders.spanOrQuery; -import static org.elasticsearch.index.query.QueryBuilders.spanTermQuery; -import static org.elasticsearch.index.query.QueryBuilders.spanWithinQuery; -import static org.elasticsearch.index.query.QueryBuilders.termQuery; -import static org.elasticsearch.index.query.QueryBuilders.termsQuery; -import static org.elasticsearch.index.query.QueryBuilders.wildcardQuery; +import static org.elasticsearch.index.query.QueryBuilders.*; import static org.elasticsearch.test.StreamsUtils.copyToBytesFromClasspath; import static org.elasticsearch.test.StreamsUtils.copyToStringFromClasspath; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBooleanSubQuery; -import static org.hamcrest.Matchers.closeTo; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.Matchers.sameInstance; +import static org.hamcrest.Matchers.*; public class SimpleIndexQueryParserTests extends ESSingleNodeTestCase { From 96f3b2868c0e10c1d3ffaa98a7a8debe84c68e2a Mon Sep 17 00:00:00 2001 From: Alex Ksikes Date: Sun, 13 Sep 2015 22:30:07 +0200 Subject: [PATCH 02/10] adds serialization and a couple of fixes --- .../org/elasticsearch/index/VersionType.java | 23 ++- .../index/query/MoreLikeThisQueryBuilder.java | 158 ++++++++++++++---- .../index/query/MoreLikeThisQueryParser.java | 16 +- .../query/MoreLikeThisQueryBuilderTests.java | 12 +- 4 files changed, 156 insertions(+), 53 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/VersionType.java b/core/src/main/java/org/elasticsearch/index/VersionType.java index 7800226c90c67..6918b92844237 100644 --- a/core/src/main/java/org/elasticsearch/index/VersionType.java +++ b/core/src/main/java/org/elasticsearch/index/VersionType.java @@ -18,12 +18,17 @@ */ package org.elasticsearch.index; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.lucene.uid.Versions; +import java.io.IOException; + /** * */ -public enum VersionType { +public enum VersionType implements Writeable { INTERNAL((byte) 0) { @Override public boolean isVersionConflictForWrites(long currentVersion, long expectedVersion) { @@ -219,6 +224,8 @@ public boolean validateVersionForReads(long version) { private final byte value; + private static final VersionType PROTOTYPE = INTERNAL; + VersionType(byte value) { this.value = value; } @@ -304,4 +311,18 @@ public static VersionType fromValue(byte value) { } throw new IllegalArgumentException("No version type match [" + value + "]"); } + + @Override + public VersionType readFrom(StreamInput in) throws IOException { + return VersionType.values()[in.readVInt()]; + } + + public static VersionType readVersionTypeFrom(StreamInput in) throws IOException { + return PROTOTYPE.readFrom(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(this.ordinal()); + } } diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java index 6d0b30b9eb842..ba03a1a62e31d 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java @@ -37,6 +37,7 @@ import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.lucene.search.MoreLikeThisQuery; import org.elasticsearch.common.lucene.search.XMoreLikeThis; import org.elasticsearch.common.lucene.uid.Versions; @@ -69,7 +70,7 @@ public class MoreLikeThisQueryBuilder extends AbstractQueryBuilder stopWords; + private String[] stopWords; private String analyzer; // query formation parameters @@ -96,14 +97,14 @@ public class MoreLikeThisQueryBuilder extends AbstractQueryBuilder { public static final Item[] EMPTY_ARRAY = new Item[0]; public interface Field { @@ -128,6 +129,8 @@ public interface Field { private long version = Versions.MATCH_ANY; private VersionType versionType = VersionType.INTERNAL; + static final Item PROTOTYPE = new Item(); + public Item() { } @@ -380,6 +383,35 @@ public final String toString() { } } + @Override + public Item readFrom(StreamInput in) throws IOException { + Item item = new Item(in.readOptionalString(), in.readOptionalString(), in.readOptionalString()); + item.doc = in.readBytesReference(); + item.fields = (String[]) in.readGenericValue(); + item.perFieldAnalyzer = (Map) in.readGenericValue(); + item.routing = in.readOptionalString(); + item.version = in.readLong(); + item.versionType = VersionType.readVersionTypeFrom(in); + return item; + } + + public static Item readItemFrom(StreamInput in) throws IOException { + return PROTOTYPE.readFrom(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeOptionalString(index); + out.writeOptionalString(type); + out.writeOptionalString(id); + out.writeBytesReference(doc); + out.writeGenericValue(fields); + out.writeGenericValue(perFieldAnalyzer); + out.writeOptionalString(routing); + out.writeLong(version); + versionType.writeTo(out); + } + @Override public int hashCode() { return Objects.hash(index, type, id, doc, Arrays.hashCode(fields), perFieldAnalyzer, routing, @@ -558,18 +590,12 @@ public MoreLikeThisQueryBuilder maxWordLength(int maxWordLength) { * reasonable to assume that "a stop word is never interesting". */ public MoreLikeThisQueryBuilder stopWords(String... stopWords) { - return stopWords(new HashSet<>(Arrays.asList(stopWords))); + this.stopWords = stopWords; + return this; } - /** - * Set the set of stopwords. - *

- *

Any word in this set is considered "uninteresting" and ignored. Even if your Analyzer allows stopwords, you - * might want to tell the MoreLikeThis code to ignore them, as for the purposes of document similarity it seems - * reasonable to assume that "a stop word is never interesting". - */ - public MoreLikeThisQueryBuilder stopWords(Set stopWords) { - this.stopWords = stopWords; + public MoreLikeThisQueryBuilder stopWords(List stopWords) { + this.stopWords = stopWords != null ? stopWords.toArray(new String[stopWords.size()]) : null; return this; } @@ -588,6 +614,9 @@ public MoreLikeThisQueryBuilder analyzer(String analyzer) { * @see org.elasticsearch.common.lucene.search.Queries#calculateMinShouldMatch(int, String) */ public MoreLikeThisQueryBuilder minimumShouldMatch(String minimumShouldMatch) { + if (minimumShouldMatch == null) { + throw new IllegalArgumentException("[" + NAME + "] requires minimum should match to be non-null"); + } this.minimumShouldMatch = minimumShouldMatch; return this; } @@ -612,7 +641,7 @@ public MoreLikeThisQueryBuilder include(boolean include) { * Whether to fail or return no result when this query is run against a field which is not supported such as binary/numeric fields. */ public MoreLikeThisQueryBuilder failOnUnsupportedField(boolean fail) { - failOnUnsupportedField = fail; + this.failOnUnsupportedField = fail; return this; } @@ -686,7 +715,7 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep builder.field(MoreLikeThisQueryParser.Field.MAX_DOC_FREQ.getPreferredName(), maxDocFreq); builder.field(MoreLikeThisQueryParser.Field.MIN_WORD_LENGTH.getPreferredName(), minWordLength); builder.field(MoreLikeThisQueryParser.Field.MAX_WORD_LENGTH.getPreferredName(), maxWordLength); - if (stopWords != null && stopWords.size() > 0) { + if (stopWords != null && stopWords.length > 0) { builder.field(MoreLikeThisQueryParser.Field.STOP_WORDS.getPreferredName(), stopWords); } if (analyzer != null) { @@ -695,9 +724,7 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep builder.field(MoreLikeThisQueryParser.Field.MINIMUM_SHOULD_MATCH.getPreferredName(), minimumShouldMatch); builder.field(MoreLikeThisQueryParser.Field.BOOST_TERMS.getPreferredName(), boostTerms); builder.field(MoreLikeThisQueryParser.Field.INCLUDE.getPreferredName(), include); - if (failOnUnsupportedField != null) { - builder.field(MoreLikeThisQueryParser.Field.FAIL_ON_UNSUPPORTED_FIELD.getPreferredName(), failOnUnsupportedField); - } + builder.field(MoreLikeThisQueryParser.Field.FAIL_ON_UNSUPPORTED_FIELD.getPreferredName(), failOnUnsupportedField); printBoostAndQueryName(builder); builder.endObject(); } @@ -714,8 +741,8 @@ private static void buildLikeField(XContentBuilder builder, String fieldName, Li } @Override - protected void setFinalBoost(Query query) { - super.setFinalBoost(query); + public String getWriteableName() { + return NAME; } @Override @@ -733,7 +760,9 @@ protected Query doToQuery(QueryShardContext context) throws IOException { mltQuery.setMinWordLen(minWordLength); mltQuery.setMaxWordLen(maxWordLength); mltQuery.setMinimumShouldMatch(minimumShouldMatch); - mltQuery.setStopWords(stopWords); + if (stopWords != null) { + mltQuery.setStopWords(new HashSet<>(Arrays.asList(stopWords))); + } // sets boost terms if (boostTerms != 0) { @@ -919,38 +948,95 @@ public QueryValidationException validate() { validationException = addValidationError("more_like_this requires 'like' to be specified.", validationException); } if (fields != null && fields.isEmpty()) { - validationException = addValidationError("more_like_this requires 'like' to be specified", validationException); + validationException = addValidationError("more_like_this requires 'fields' to be specified", validationException); } return validationException; } @Override protected MoreLikeThisQueryBuilder doReadFrom(StreamInput in) throws IOException { - return super.doReadFrom(in); + MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = new MoreLikeThisQueryBuilder((List) in.readGenericValue()); + moreLikeThisQueryBuilder.likeTexts = (List) in.readGenericValue(); + moreLikeThisQueryBuilder.unlikeTexts = (List) in.readGenericValue(); + moreLikeThisQueryBuilder.likeItems = readItems(in); + moreLikeThisQueryBuilder.unlikeItems = readItems(in); + moreLikeThisQueryBuilder.maxQueryTerms = in.readVInt(); + moreLikeThisQueryBuilder.minTermFreq = in.readVInt(); + moreLikeThisQueryBuilder.minDocFreq = in.readVInt(); + moreLikeThisQueryBuilder.maxDocFreq = in.readVInt(); + moreLikeThisQueryBuilder.minWordLength = in.readVInt(); + moreLikeThisQueryBuilder.maxWordLength = in.readVInt(); + moreLikeThisQueryBuilder.stopWords = (String[]) in.readGenericValue(); + moreLikeThisQueryBuilder.analyzer = in.readOptionalString(); + moreLikeThisQueryBuilder.minimumShouldMatch = in.readString(); + moreLikeThisQueryBuilder.boostTerms = (Float) in.readGenericValue(); + moreLikeThisQueryBuilder.include = in.readBoolean(); + moreLikeThisQueryBuilder.failOnUnsupportedField = in.readBoolean(); + return moreLikeThisQueryBuilder; } - @Override - protected void doWriteTo(StreamOutput out) throws IOException { - super.doWriteTo(out); + private static List readItems(StreamInput in) throws IOException { + List items = new ArrayList<>(); + int size = in.readVInt(); + for (int i = 0; i < size; i++) { + items.add(Item.readItemFrom(in)); + } + return items; } @Override - protected boolean doEquals(MoreLikeThisQueryBuilder other) { - return super.doEquals(other); + protected void doWriteTo(StreamOutput out) throws IOException { + out.writeGenericValue(fields); + out.writeGenericValue(likeTexts); + out.writeGenericValue(unlikeTexts); + writeItems(likeItems, out); + writeItems(unlikeItems, out); + out.writeVInt(maxQueryTerms); + out.writeVInt(minTermFreq); + out.writeVInt(minDocFreq); + out.writeVInt(maxDocFreq); + out.writeVInt(minWordLength); + out.writeVInt(maxWordLength); + out.writeGenericValue(stopWords); + out.writeOptionalString(analyzer); + out.writeString(minimumShouldMatch); + out.writeGenericValue(boostTerms); + out.writeBoolean(include); + out.writeBoolean(failOnUnsupportedField); } - @Override - protected int doHashCode() { - return super.doHashCode(); + private static void writeItems(List items, StreamOutput out) throws IOException { + out.writeVInt(items.size()); + for (Item item : items) { + item.writeTo(out); + } } @Override - public String getName() { - return super.getName(); + protected int doHashCode() { + return Objects.hash(fields, likeTexts, unlikeTexts, likeItems, unlikeItems, maxQueryTerms, minTermFreq, + minDocFreq, maxDocFreq, minWordLength, maxWordLength, stopWords, analyzer, minimumShouldMatch, + boostTerms, include, failOnUnsupportedField); } @Override - public String getWriteableName() { - return NAME; + protected boolean doEquals(MoreLikeThisQueryBuilder other) { + return Objects.equals(fields, other.fields) && + Objects.equals(likeTexts, other.likeTexts) && + Objects.equals(unlikeTexts, other.unlikeTexts) && + Objects.equals(likeItems, other.likeItems) && + Objects.equals(unlikeItems, other.unlikeItems) && + Objects.equals(maxQueryTerms, other.maxQueryTerms) && + Objects.equals(minTermFreq, other.minTermFreq) && + Objects.equals(minDocFreq, other.minDocFreq) && + Objects.equals(maxDocFreq, other.maxDocFreq) && + Objects.equals(minWordLength, other.minWordLength) && + Objects.equals(maxWordLength, other.maxWordLength) && + Objects.equals(stopWords, other.stopWords) && + Objects.equals(analyzer, other.analyzer) && + Objects.equals(minimumShouldMatch, other.minimumShouldMatch) && + Objects.equals(boostTerms, other.boostTerms) && + Objects.equals(include, other.include) && + Objects.equals(failOnUnsupportedField, other.failOnUnsupportedField); } } diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java index ec503de686ea5..747d2c7b44a3b 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java @@ -19,7 +19,6 @@ package org.elasticsearch.index.query; -import com.google.common.collect.Sets; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.MoreLikeThisQueryBuilder.Item; @@ -27,7 +26,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Set; /** * Parser for the The More Like This Query (MLT Query) which finds documents that are "like" a given set of documents. @@ -71,7 +69,7 @@ public MoreLikeThisQueryBuilder fromXContent(QueryParseContext parseContext) thr XContentParser parser = parseContext.parser(); // document inputs - List moreLikeFields = null; + List fields = null; List likeTexts = new ArrayList<>(); List unlikeTexts = new ArrayList<>(); List likeItems = new ArrayList<>(); @@ -84,7 +82,7 @@ public MoreLikeThisQueryBuilder fromXContent(QueryParseContext parseContext) thr int maxDocFreq = MoreLikeThisQueryBuilder.DEFAULT_MAX_DOC_FREQ; int minWordLength = MoreLikeThisQueryBuilder.DEFAULT_MIN_WORD_LENGTH; int maxWordLength = MoreLikeThisQueryBuilder.DEFAULT_MAX_WORD_LENGTH; - Set stopWords = null; + List stopWords = null; String analyzer = null; // query formation parameters @@ -140,9 +138,9 @@ public MoreLikeThisQueryBuilder fromXContent(QueryParseContext parseContext) thr } } else if (token == XContentParser.Token.START_ARRAY) { if (parseContext.parseFieldMatcher().match(currentFieldName, Field.FIELDS)) { - moreLikeFields = new ArrayList<>(); + fields = new ArrayList<>(); while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - moreLikeFields.add(parser.text()); + fields.add(parser.text()); } } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.LIKE)) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { @@ -167,7 +165,7 @@ public MoreLikeThisQueryBuilder fromXContent(QueryParseContext parseContext) thr likeItems.add(Item.parse(parser, parseContext.parseFieldMatcher(), new Item())); } } else if (parseContext.parseFieldMatcher().match(currentFieldName, Field.STOP_WORDS)) { - stopWords = Sets.newHashSet(); + stopWords = new ArrayList<>(); while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { stopWords.add(parser.text()); } @@ -188,11 +186,11 @@ public MoreLikeThisQueryBuilder fromXContent(QueryParseContext parseContext) thr if (likeTexts.isEmpty() && likeItems.isEmpty()) { throw new QueryParsingException(parseContext, "more_like_this requires 'like' to be specified"); } - if (moreLikeFields != null && moreLikeFields.isEmpty()) { + if (fields != null && fields.isEmpty()) { throw new QueryParsingException(parseContext, "more_like_this requires 'fields' to be non-empty"); } - return new MoreLikeThisQueryBuilder(moreLikeFields) + return new MoreLikeThisQueryBuilder(fields) .like(likeTexts.toArray(new String[likeTexts.size()])) .unlike(unlikeTexts.toArray(new String[unlikeTexts.size()])) .like(likeItems.toArray(new Item[likeItems.size()])) diff --git a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java index 81150d9d4ca37..1a7c8005b61a2 100644 --- a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java @@ -37,12 +37,16 @@ public void randomItems() { } - @Override protected MoreLikeThisQueryBuilder doCreateTestQueryBuilder() { return null; } + @Override + protected MultiTermVectorsResponse executeMultiTermVectors(MultiTermVectorsRequest mtvRequest) { + throw new UnsupportedOperationException("this test can't handle MultiTermVector requests"); + } + @Override protected void doAssertLuceneQuery(MoreLikeThisQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException { @@ -52,10 +56,4 @@ protected void doAssertLuceneQuery(MoreLikeThisQueryBuilder queryBuilder, Query public void testValidate() { } - - @Override - protected MultiTermVectorsResponse executeMultiTermVectors(MultiTermVectorsRequest mtvRequest) { - throw new UnsupportedOperationException("this test can't handle MultiTermVector requests"); - } - } From f060dbea867a47277f9627a93d617d2ed349e821 Mon Sep 17 00:00:00 2001 From: Alex Ksikes Date: Mon, 14 Sep 2015 15:02:19 +0200 Subject: [PATCH 03/10] adds most builder tests --- .../common/io/stream/StreamInput.java | 7 + .../common/io/stream/StreamOutput.java | 12 + .../index/query/MoreLikeThisQueryBuilder.java | 18 +- .../query/MoreLikeThisQueryBuilderTests.java | 213 +++++++++++++++++- .../morelikethis/ItemSerializationTests.java | 60 ----- 5 files changed, 234 insertions(+), 76 deletions(-) delete mode 100644 core/src/test/java/org/elasticsearch/search/morelikethis/ItemSerializationTests.java diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java b/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java index c2bbaa3d5e913..8743d11ca1871 100644 --- a/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java +++ b/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java @@ -350,6 +350,13 @@ public String[] readStringArray() throws IOException { return ret; } + public String[] readOptionalStringArray() throws IOException { + if (readBoolean()) { + return readStringArray(); + } + return null; + } + @Nullable @SuppressWarnings("unchecked") public Map readMap() throws IOException { diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java index a8089198f290c..77f1fb456e547 100644 --- a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java +++ b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java @@ -316,6 +316,18 @@ public void writeStringArrayNullable(@Nullable String[] array) throws IOExceptio } } + /** + * Writes a string array, for nullable string, writes it as 0 (empty string). + */ + public void writeOptionalStringArray(@Nullable String[] array) throws IOException { + if (array == null) { + writeBoolean(false); + } else { + writeBoolean(true); + writeStringArray(array); + } + } + public void writeMap(@Nullable Map map) throws IOException { writeGenericValue(map); } diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java index ba03a1a62e31d..c05ca138f7747 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java @@ -386,8 +386,8 @@ public final String toString() { @Override public Item readFrom(StreamInput in) throws IOException { Item item = new Item(in.readOptionalString(), in.readOptionalString(), in.readOptionalString()); - item.doc = in.readBytesReference(); - item.fields = (String[]) in.readGenericValue(); + item.doc = (BytesReference) in.readGenericValue(); + item.fields = in.readOptionalStringArray(); item.perFieldAnalyzer = (Map) in.readGenericValue(); item.routing = in.readOptionalString(); item.version = in.readLong(); @@ -404,8 +404,8 @@ public void writeTo(StreamOutput out) throws IOException { out.writeOptionalString(index); out.writeOptionalString(type); out.writeOptionalString(id); - out.writeBytesReference(doc); - out.writeGenericValue(fields); + out.writeGenericValue(doc); + out.writeOptionalStringArray(fields); out.writeGenericValue(perFieldAnalyzer); out.writeOptionalString(routing); out.writeLong(version); @@ -715,7 +715,7 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep builder.field(MoreLikeThisQueryParser.Field.MAX_DOC_FREQ.getPreferredName(), maxDocFreq); builder.field(MoreLikeThisQueryParser.Field.MIN_WORD_LENGTH.getPreferredName(), minWordLength); builder.field(MoreLikeThisQueryParser.Field.MAX_WORD_LENGTH.getPreferredName(), maxWordLength); - if (stopWords != null && stopWords.length > 0) { + if (stopWords != null) { builder.field(MoreLikeThisQueryParser.Field.STOP_WORDS.getPreferredName(), stopWords); } if (analyzer != null) { @@ -966,7 +966,7 @@ protected MoreLikeThisQueryBuilder doReadFrom(StreamInput in) throws IOException moreLikeThisQueryBuilder.maxDocFreq = in.readVInt(); moreLikeThisQueryBuilder.minWordLength = in.readVInt(); moreLikeThisQueryBuilder.maxWordLength = in.readVInt(); - moreLikeThisQueryBuilder.stopWords = (String[]) in.readGenericValue(); + moreLikeThisQueryBuilder.stopWords = in.readOptionalStringArray(); moreLikeThisQueryBuilder.analyzer = in.readOptionalString(); moreLikeThisQueryBuilder.minimumShouldMatch = in.readString(); moreLikeThisQueryBuilder.boostTerms = (Float) in.readGenericValue(); @@ -997,7 +997,7 @@ protected void doWriteTo(StreamOutput out) throws IOException { out.writeVInt(maxDocFreq); out.writeVInt(minWordLength); out.writeVInt(maxWordLength); - out.writeGenericValue(stopWords); + out.writeOptionalStringArray(stopWords); out.writeOptionalString(analyzer); out.writeString(minimumShouldMatch); out.writeGenericValue(boostTerms); @@ -1015,7 +1015,7 @@ private static void writeItems(List items, StreamOutput out) throws IOExce @Override protected int doHashCode() { return Objects.hash(fields, likeTexts, unlikeTexts, likeItems, unlikeItems, maxQueryTerms, minTermFreq, - minDocFreq, maxDocFreq, minWordLength, maxWordLength, stopWords, analyzer, minimumShouldMatch, + minDocFreq, maxDocFreq, minWordLength, maxWordLength, Arrays.hashCode(stopWords), analyzer, minimumShouldMatch, boostTerms, include, failOnUnsupportedField); } @@ -1032,7 +1032,7 @@ protected boolean doEquals(MoreLikeThisQueryBuilder other) { Objects.equals(maxDocFreq, other.maxDocFreq) && Objects.equals(minWordLength, other.minWordLength) && Objects.equals(maxWordLength, other.maxWordLength) && - Objects.equals(stopWords, other.stopWords) && + Arrays.equals(stopWords, other.stopWords) && // otherwise we are comparing pointers Objects.equals(analyzer, other.analyzer) && Objects.equals(minimumShouldMatch, other.minimumShouldMatch) && Objects.equals(boostTerms, other.boostTerms) && diff --git a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java index 1a7c8005b61a2..b35f3093c341c 100644 --- a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java @@ -19,32 +19,166 @@ package org.elasticsearch.index.query; +import org.apache.lucene.analysis.core.WhitespaceAnalyzer; +import org.apache.lucene.index.Fields; +import org.apache.lucene.index.MultiFields; +import org.apache.lucene.index.memory.MemoryIndex; import org.apache.lucene.search.Query; -import org.elasticsearch.action.termvectors.MultiTermVectorsRequest; -import org.elasticsearch.action.termvectors.MultiTermVectorsResponse; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.termvectors.*; +import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.VersionType; +import org.elasticsearch.index.query.MoreLikeThisQueryBuilder.Item; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import java.io.IOException; -import java.util.List; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.is; public class MoreLikeThisQueryBuilderTests extends AbstractQueryTestCase { - private List randomItems; + private static String[] randomFields; + private static Item[] randomLikeItems; + private static Item[] randomUnlikeItems; @Before - public void randomItems() { + public void setup() { + // MLT only supports string fields, unsupported fields are tested below + randomFields = randomStringFields(); + // we also preset the item requests + randomLikeItems = new Item[randomIntBetween(1, 3)]; + for (int i = 0; i < randomLikeItems.length; i++) { + randomLikeItems[i] = generateRandomItem(); + } + // and for the unlike items too + randomUnlikeItems = new Item[randomIntBetween(1, 3)]; + for (int i = 0; i < randomUnlikeItems.length; i++) { + randomUnlikeItems[i] = generateRandomItem(); + } + } + private static String[] randomStringFields() { + String[] mappedStringFields = new String[]{STRING_FIELD_NAME, STRING_FIELD_NAME_2}; + String[] unmappedStringFields = generateRandomStringArray(2, 5, false, false); + return Stream.concat(Arrays.stream(mappedStringFields), Arrays.stream(unmappedStringFields)).toArray(String[]::new); + } + + private Item generateRandomItem() { + Item item = new Item( + randomBoolean() ? getIndex().getName() : null, + getRandomType(), // set to one type to avoid ambiguous types + randomAsciiOfLength(5)) + .routing(randomAsciiOfLength(10)) + .version(randomInt(5)) + .versionType(randomFrom(VersionType.values())); + // if no field is specified MLT uses all mapped fields for this item + if (randomBoolean()) { + item.fields(randomFrom(randomFields)); + } + return item; + // delegate artificial documents and per field analyzers to the tests further below } @Override protected MoreLikeThisQueryBuilder doCreateTestQueryBuilder() { - return null; + MoreLikeThisQueryBuilder queryBuilder; + if (randomBoolean()) { // for the default field + queryBuilder = new MoreLikeThisQueryBuilder(); + } else { + queryBuilder = new MoreLikeThisQueryBuilder(randomFields); + } + // like field is required + if (randomBoolean()) { + queryBuilder.like(generateRandomStringArray(5, 5, false, false)); + } else { + queryBuilder.like(randomLikeItems); + } + if (randomBoolean()) { + queryBuilder.unlike(generateRandomStringArray(5, 5, false, false)); + } + if (randomBoolean()) { + queryBuilder.unlike(randomUnlikeItems); + } + if (randomBoolean()) { + queryBuilder.maxQueryTerms(randomInt(25)); + } + if (randomBoolean()) { + queryBuilder.minTermFreq(randomInt(5)); + } + if (randomBoolean()) { + queryBuilder.minDocFreq(randomInt(5)); + } + if (randomBoolean()) { + queryBuilder.maxDocFreq(randomInt(100)); + } + if (randomBoolean()) { + queryBuilder.minWordLength(randomInt(5)); + } + if (randomBoolean()) { + queryBuilder.maxWordLength(randomInt(25)); + } + if (randomBoolean()) { + queryBuilder.stopWords(generateRandomStringArray(5, 5, false)); + } + if (randomBoolean()) { + queryBuilder.analyzer(randomFrom("simple", "keyword", "whitespace")); // fix the analyzer? + } + if (randomBoolean()) { + queryBuilder.minimumShouldMatch(randomMinimumShouldMatch()); + } + if (randomBoolean()) { + queryBuilder.boostTerms(randomFloat() * 10); + } + if (randomBoolean()) { + queryBuilder.include(true); + } + if (randomBoolean()) { + queryBuilder.failOnUnsupportedField(false); + } + return queryBuilder; } @Override protected MultiTermVectorsResponse executeMultiTermVectors(MultiTermVectorsRequest mtvRequest) { - throw new UnsupportedOperationException("this test can't handle MultiTermVector requests"); + try { + MultiTermVectorsItemResponse[] responses = new MultiTermVectorsItemResponse[mtvRequest.size()]; + int i = 0; + for (TermVectorsRequest request : mtvRequest) { + TermVectorsResponse response = new TermVectorsResponse(request.index(), request.type(), request.id()); + response.setExists(true); + Fields generatedFields = generateFields(request.selectedFields().toArray(new String[0]), request.id()); + EnumSet flags = EnumSet.of(TermVectorsRequest.Flag.Positions, TermVectorsRequest.Flag.Offsets); + response.setFields(generatedFields, request.selectedFields(), flags, generatedFields); + responses[i++] = new MultiTermVectorsItemResponse(response, null); + } + return new MultiTermVectorsResponse(responses); + } catch (IOException ex) { + throw new ElasticsearchException("boom", ex); + } + } + + /** + * Here we could go overboard and use a pre-generated indexed random document for a given Item, + * but for now we'd prefer to simply return the id as the content of the document and that for + * every field. + */ + private static Fields generateFields(String[] fieldNames, String text) throws IOException { + MemoryIndex index = new MemoryIndex(); + for (String fieldName : fieldNames) { + index.addField(fieldName, text, new WhitespaceAnalyzer()); + } + return MultiFields.getFields(index.createSearcher().getIndexReader()); } @Override @@ -54,6 +188,71 @@ protected void doAssertLuceneQuery(MoreLikeThisQueryBuilder queryBuilder, Query @Test public void testValidate() { + MoreLikeThisQueryBuilder queryBuilder = new MoreLikeThisQueryBuilder(Strings.EMPTY_ARRAY); + assertThat(queryBuilder.validate().validationErrors().size(), is(2)); + + queryBuilder = new MoreLikeThisQueryBuilder(Strings.EMPTY_ARRAY).like("some text"); + assertThat(queryBuilder.validate().validationErrors().size(), is(1)); + + queryBuilder = new MoreLikeThisQueryBuilder("field").like(Strings.EMPTY_ARRAY); + assertThat(queryBuilder.validate().validationErrors().size(), is(1)); + + queryBuilder = new MoreLikeThisQueryBuilder("field").like(Item.EMPTY_ARRAY); + assertThat(queryBuilder.validate().validationErrors().size(), is(1)); + + queryBuilder = new MoreLikeThisQueryBuilder("field").like("some text"); + assertNull(queryBuilder.validate()); + } + + @Test + public void testWithArtificialDocument() { + + } + + @Test + public void testUnsupportedFields() throws IOException { + String unsupportedField = randomFrom(INT_FIELD_NAME, DOUBLE_FIELD_NAME, DATE_FIELD_NAME); + MoreLikeThisQueryBuilder queryBuilder = new MoreLikeThisQueryBuilder(unsupportedField) + .like("some text") + .failOnUnsupportedField(true); + try { + queryBuilder.toQuery(createShardContext()); + fail("should have failed with IllegalArgumentException for field: " + unsupportedField); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), Matchers.containsString("more_like_this doesn't support binary/numeric fields")); + } + } + + @Test + public void testWithItemNullId() { + + } + + @Test + public void testWithItemNullIdAndNullArtificialDocument() { + + } + + @Test + public void testItemSerialization() throws IOException { + Item expectedItem = generateRandomItem(); + BytesStreamOutput output = new BytesStreamOutput(); + expectedItem.writeTo(output); + Item newItem = Item.readItemFrom(StreamInput.wrap(output.bytes())); + assertEquals(expectedItem, newItem); + } + + @Test + public void testItemFromXContent() throws IOException { + Item expectedItem = generateRandomItem(); + String json = expectedItem.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS).string(); + XContentParser parser = XContentFactory.xContent(json).createParser(json); + Item newItem = Item.parse(parser, ParseFieldMatcher.STRICT, new Item()); + assertEquals(expectedItem, newItem); + } + + @Test + public void testPerFieldAnalyzer() { } } diff --git a/core/src/test/java/org/elasticsearch/search/morelikethis/ItemSerializationTests.java b/core/src/test/java/org/elasticsearch/search/morelikethis/ItemSerializationTests.java deleted file mode 100644 index 5f5f42aa7b247..0000000000000 --- a/core/src/test/java/org/elasticsearch/search/morelikethis/ItemSerializationTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch 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.elasticsearch.search.morelikethis; - -import com.carrotsearch.randomizedtesting.generators.RandomPicks; -import org.elasticsearch.common.ParseFieldMatcher; -import org.elasticsearch.common.xcontent.ToXContent; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.VersionType; -import org.elasticsearch.index.query.MoreLikeThisQueryBuilder.Item; -import org.elasticsearch.test.ESTestCase; -import org.junit.Test; - -import java.util.Random; - -public class ItemSerializationTests extends ESTestCase { - - private Item generateRandomItem(int arraySize, int stringSize) { - String index = randomAsciiOfLength(stringSize); - String type = randomAsciiOfLength(stringSize); - String id = String.valueOf(Math.abs(randomInt())); - String[] fields = generateRandomStringArray(arraySize, stringSize, true); - String routing = randomBoolean() ? randomAsciiOfLength(stringSize) : null; - long version = Math.abs(randomLong()); - VersionType versionType = RandomPicks.randomFrom(new Random(), VersionType.values()); - return new Item(index, type, id).fields(fields).routing(routing).version(version).versionType(versionType); - } - - @Test - public void testItemSerialization() throws Exception { - int numOfTrials = 100; - int maxArraySize = 7; - int maxStringSize = 8; - for (int i = 0; i < numOfTrials; i++) { - Item item1 = generateRandomItem(maxArraySize, maxStringSize); - String json = item1.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS).string(); - XContentParser parser = XContentFactory.xContent(json).createParser(json); - Item item2 = Item.parse(parser, ParseFieldMatcher.STRICT, new Item()); - assertEquals(item1, item2); - } - } -} From c00cac326f869101add2bd3eb3089e7581e41c74 Mon Sep 17 00:00:00 2001 From: Alex Ksikes Date: Mon, 14 Sep 2015 16:21:25 +0200 Subject: [PATCH 04/10] adds item validation --- .../index/query/MoreLikeThisQueryBuilder.java | 36 +++++++++++++---- .../query/MoreLikeThisQueryBuilderTests.java | 39 ++++++++++++------- 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java index c05ca138f7747..97e5ec82ee90b 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java @@ -204,7 +204,10 @@ public Item doc(BytesReference doc) { * Sets to a given artificial document, that is a document that is not present in the index. */ public Item doc(XContentBuilder doc) { - return this.doc(doc.bytes()); + if (doc != null) { + this.doc(doc.bytes()); + } + return this; } public String[] fields() { @@ -383,6 +386,15 @@ public final String toString() { } } + public QueryValidationException validate() { + QueryValidationException validationException = null; + if (id == null && doc == null) { + validationException = new QueryValidationException(); + validationException.addValidationError("item must either have an '_id' or be an artificial 'doc'"); + } + return validationException; + } + @Override public Item readFrom(StreamInput in) throws IOException { Item item = new Item(in.readOptionalString(), in.readOptionalString(), in.readOptionalString()); @@ -701,11 +713,7 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep if (fields != null) { builder.field(MoreLikeThisQueryParser.Field.FIELDS.getPreferredName(), fields); } - if (this.likeTexts.isEmpty() && this.likeItems.isEmpty()) { - throw new IllegalArgumentException("more_like_this requires '" + MoreLikeThisQueryParser.Field.LIKE.getPreferredName() + "' to be provided"); - } else { - buildLikeField(builder, MoreLikeThisQueryParser.Field.LIKE.getPreferredName(), likeTexts, likeItems); - } + buildLikeField(builder, MoreLikeThisQueryParser.Field.LIKE.getPreferredName(), likeTexts, likeItems); if (!unlikeTexts.isEmpty() || !unlikeItems.isEmpty()) { buildLikeField(builder, MoreLikeThisQueryParser.Field.UNLIKE.getPreferredName(), unlikeTexts, unlikeItems); } @@ -945,10 +953,22 @@ private static void handleExclude(BooleanQuery boolQuery, List likeItems) public QueryValidationException validate() { QueryValidationException validationException = null; if (likeTexts.isEmpty() && likeItems.isEmpty()) { - validationException = addValidationError("more_like_this requires 'like' to be specified.", validationException); + validationException = addValidationError("requires 'like' to be specified.", validationException); } if (fields != null && fields.isEmpty()) { - validationException = addValidationError("more_like_this requires 'fields' to be specified", validationException); + validationException = addValidationError("requires 'fields' to be specified", validationException); + } + for (Item likeItem : likeItems) { + QueryValidationException validate = likeItem.validate(); + if (validate != null) { + validationException = addValidationError(validate.getMessage(), validationException); + } + } + for (Item unlikeItem : unlikeItems) { + QueryValidationException validate = unlikeItem.validate(); + if (validate != null) { + validationException = addValidationError(validate.getMessage(), validationException); + } } return validationException; } diff --git a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java index b35f3093c341c..86e747bd4515b 100644 --- a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java @@ -23,6 +23,7 @@ import org.apache.lucene.index.Fields; import org.apache.lucene.index.MultiFields; import org.apache.lucene.index.memory.MemoryIndex; +import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Query; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.termvectors.*; @@ -30,7 +31,9 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.lucene.search.MoreLikeThisQuery; import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.VersionType; @@ -44,7 +47,7 @@ import java.util.EnumSet; import java.util.stream.Stream; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; public class MoreLikeThisQueryBuilderTests extends AbstractQueryTestCase { @@ -183,7 +186,7 @@ private static Fields generateFields(String[] fieldNames, String text) throws IO @Override protected void doAssertLuceneQuery(MoreLikeThisQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException { - + assertThat(query, anyOf(instanceOf(BooleanQuery.class), instanceOf(MoreLikeThisQuery.class))); } @Test @@ -205,12 +208,32 @@ public void testValidate() { } @Test - public void testWithArtificialDocument() { + public void testValidateItems() { + MoreLikeThisQueryBuilder queryBuilder = new MoreLikeThisQueryBuilder("field").like("some text"); + int totalExpectedErrors = 0; + if (randomBoolean()) { + queryBuilder.addLikeItem(generateRandomItem().id(null)); + totalExpectedErrors++; + } + if (randomBoolean()) { + queryBuilder.addLikeItem(generateRandomItem().id(null).doc((XContentBuilder) null)); + totalExpectedErrors++; + } + if (randomBoolean()) { + queryBuilder.addUnlikeItem(generateRandomItem().id(null)); + totalExpectedErrors++; + } + assertValidate(queryBuilder, totalExpectedErrors); + } + + @Test + public void testArtificialDocument() { } @Test public void testUnsupportedFields() throws IOException { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); String unsupportedField = randomFrom(INT_FIELD_NAME, DOUBLE_FIELD_NAME, DATE_FIELD_NAME); MoreLikeThisQueryBuilder queryBuilder = new MoreLikeThisQueryBuilder(unsupportedField) .like("some text") @@ -223,16 +246,6 @@ public void testUnsupportedFields() throws IOException { } } - @Test - public void testWithItemNullId() { - - } - - @Test - public void testWithItemNullIdAndNullArtificialDocument() { - - } - @Test public void testItemSerialization() throws IOException { Item expectedItem = generateRandomItem(); From 3d817f4e0b3c62661dbb265b8ec2eedfe4002038 Mon Sep 17 00:00:00 2001 From: Alex Ksikes Date: Mon, 14 Sep 2015 16:30:21 +0200 Subject: [PATCH 05/10] adds getters --- .../index/query/MoreLikeThisQueryBuilder.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java index 97e5ec82ee90b..5459be08a9d50 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java @@ -472,6 +472,10 @@ public MoreLikeThisQueryBuilder(List fields) { this.fields = fields; } + public List fields() { + return fields; + } + /** * Sets the text to use in order to find documents that are "like" this. * @@ -482,6 +486,10 @@ public MoreLikeThisQueryBuilder like(String... likeTexts) { return addLikeText(likeTexts); } + public List likeTexts() { + return likeTexts; + } + /** * Sets the documents to use in order to find documents that are "like" this. * @@ -492,6 +500,10 @@ public MoreLikeThisQueryBuilder like(Item... likeItems) { return addLikeItem(likeItems); } + public List likeItems() { + return likeItems; + } + /** * Adds some text to use in order to find documents that are "like" this. */ @@ -516,6 +528,10 @@ public MoreLikeThisQueryBuilder unlike(String... unlikeTexts) { return addUnlikeText(unlikeTexts); } + public List unlikeTexts() { + return unlikeTexts; + } + /** * Sets the documents from which the terms should not be selected from. */ @@ -524,6 +540,10 @@ public MoreLikeThisQueryBuilder unlike(Item... unlikeItems) { return addUnlikeItem(unlikeItems); } + public List unlikeItems() { + return unlikeItems; + } + /** * Adds some text to use in order to find documents that are "unlike" this. */ @@ -549,6 +569,10 @@ public MoreLikeThisQueryBuilder maxQueryTerms(int maxQueryTerms) { return this; } + public int maxQueryTerms() { + return maxQueryTerms; + } + /** * The frequency below which terms will be ignored in the source doc. The default * frequency is 2. @@ -558,6 +582,10 @@ public MoreLikeThisQueryBuilder minTermFreq(int minTermFreq) { return this; } + public int minTermFreq() { + return minTermFreq; + } + /** * Sets the frequency at which words will be ignored which do not occur in at least this * many docs. Defaults to 5. @@ -567,6 +595,10 @@ public MoreLikeThisQueryBuilder minDocFreq(int minDocFreq) { return this; } + public int minDocFreq() { + return minDocFreq; + } + /** * Set the maximum frequency in which words may still appear. Words that appear * in more than this many docs will be ignored. Defaults to unbounded. @@ -576,6 +608,10 @@ public MoreLikeThisQueryBuilder maxDocFreq(int maxDocFreq) { return this; } + public int maxDocFreq() { + return maxDocFreq; + } + /** * Sets the minimum word length below which words will be ignored. Defaults * to 0. @@ -585,6 +621,10 @@ public MoreLikeThisQueryBuilder minWordLength(int minWordLength) { return this; } + public int minWordLength() { + return minWordLength; + } + /** * Sets the maximum word length above which words will be ignored. Defaults to * unbounded (0). @@ -594,6 +634,10 @@ public MoreLikeThisQueryBuilder maxWordLength(int maxWordLength) { return this; } + public int maxWordLength() { + return maxWordLength; + } + /** * Set the set of stopwords. *

@@ -611,6 +655,10 @@ public MoreLikeThisQueryBuilder stopWords(List stopWords) { return this; } + public String[] stopWords() { + return stopWords; + } + /** * The analyzer that will be used to analyze the text. Defaults to the analyzer associated with the fied. */ @@ -619,6 +667,10 @@ public MoreLikeThisQueryBuilder analyzer(String analyzer) { return this; } + public String analyzer() { + return analyzer; + } + /** * Number of terms that must match the generated query expressed in the * common syntax for minimum should match. Defaults to 30%. @@ -633,6 +685,10 @@ public MoreLikeThisQueryBuilder minimumShouldMatch(String minimumShouldMatch) { return this; } + public String minimumShouldMatch() { + return minimumShouldMatch; + } + /** * Sets the boost factor to use when boosting terms. Defaults to 0 (deactivated). */ @@ -641,6 +697,10 @@ public MoreLikeThisQueryBuilder boostTerms(float boostTerms) { return this; } + public float boostTerms() { + return boostTerms; + } + /** * Whether to include the input documents. Defaults to false */ @@ -649,6 +709,10 @@ public MoreLikeThisQueryBuilder include(boolean include) { return this; } + public boolean include() { + return include; + } + /** * Whether to fail or return no result when this query is run against a field which is not supported such as binary/numeric fields. */ @@ -657,6 +721,10 @@ public MoreLikeThisQueryBuilder failOnUnsupportedField(boolean fail) { return this; } + public boolean failOnUnsupportedField() { + return failOnUnsupportedField; + } + /** * The text to use in order to find documents that are "like" this. */ From 9239c7cb09a4f25f006a155fa6f2ed3976e04a2b Mon Sep 17 00:00:00 2001 From: Alex Ksikes Date: Mon, 14 Sep 2015 17:35:55 +0200 Subject: [PATCH 06/10] adds serialization test for artificial doc and per field analyzer --- .../index/query/MoreLikeThisQueryBuilder.java | 8 +- .../query/MoreLikeThisQueryBuilderTests.java | 84 ++++++++++++++----- 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java index 5459be08a9d50..d50e1c30defd4 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java @@ -204,7 +204,9 @@ public Item doc(BytesReference doc) { * Sets to a given artificial document, that is a document that is not present in the index. */ public Item doc(XContentBuilder doc) { - if (doc != null) { + if (doc == null) { + this.doc = null; + } else { this.doc(doc.bytes()); } return this; @@ -392,6 +394,10 @@ public QueryValidationException validate() { validationException = new QueryValidationException(); validationException.addValidationError("item must either have an '_id' or be an artificial 'doc'"); } + if (id != null && doc != null) { + validationException = validationException == null ? new QueryValidationException() : validationException; + validationException.addValidationError("either 'id' or 'doc' can be specified, but not both!"); + } return validationException; } diff --git a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java index 86e747bd4515b..bcb81f9ced50d 100644 --- a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java @@ -29,6 +29,7 @@ import org.elasticsearch.action.termvectors.*; import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.lucene.search.MoreLikeThisQuery; @@ -45,9 +46,13 @@ import java.io.IOException; import java.util.Arrays; import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; import java.util.stream.Stream; -import static org.hamcrest.Matchers.*; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; public class MoreLikeThisQueryBuilderTests extends AbstractQueryTestCase { @@ -78,10 +83,9 @@ private static String[] randomStringFields() { } private Item generateRandomItem() { - Item item = new Item( - randomBoolean() ? getIndex().getName() : null, - getRandomType(), // set to one type to avoid ambiguous types - randomAsciiOfLength(5)) + Item item = new Item() + .index(randomBoolean() ? getIndex().getName() : null) + .type(getRandomType()) // set to one type to avoid ambiguous types .routing(randomAsciiOfLength(10)) .version(randomInt(5)) .versionType(randomFrom(VersionType.values())); @@ -89,8 +93,38 @@ private Item generateRandomItem() { if (randomBoolean()) { item.fields(randomFrom(randomFields)); } + // id or artificial doc + if (randomBoolean()) { + item.id(randomAsciiOfLength(10)); + } else { + item.doc(randomArtificialDoc()); + } + // per field analyzer + if (randomBoolean()) { + item.perFieldAnalyzer(randomPerFieldAnalyzer()); + } return item; - // delegate artificial documents and per field analyzers to the tests further below + } + + private XContentBuilder randomArtificialDoc() { + XContentBuilder doc; + try { + doc = jsonBuilder().startObject(); + for (String field : randomFields) { + doc.field(field, randomAsciiOfLength(10)); + } + } catch (IOException e) { + throw new ElasticsearchException("Unable to generate random artificial doc!"); + } + return doc; + } + + private Map randomPerFieldAnalyzer() { + Map perFieldAnalyzer = new HashMap<>(); + for (String field : randomFields) { + perFieldAnalyzer.put(field, randomAnalyzer()); + } + return perFieldAnalyzer; } @Override @@ -135,7 +169,7 @@ protected MoreLikeThisQueryBuilder doCreateTestQueryBuilder() { queryBuilder.stopWords(generateRandomStringArray(5, 5, false)); } if (randomBoolean()) { - queryBuilder.analyzer(randomFrom("simple", "keyword", "whitespace")); // fix the analyzer? + queryBuilder.analyzer(randomAnalyzer()); // fix the analyzer? } if (randomBoolean()) { queryBuilder.minimumShouldMatch(randomMinimumShouldMatch()); @@ -160,7 +194,12 @@ protected MultiTermVectorsResponse executeMultiTermVectors(MultiTermVectorsReque for (TermVectorsRequest request : mtvRequest) { TermVectorsResponse response = new TermVectorsResponse(request.index(), request.type(), request.id()); response.setExists(true); - Fields generatedFields = generateFields(request.selectedFields().toArray(new String[0]), request.id()); + Fields generatedFields; + if (request.doc() != null) { + generatedFields = generateFields(randomFields, request.doc().toUtf8()); + } else { + generatedFields = generateFields(request.selectedFields().toArray(new String[0]), request.id()); + } EnumSet flags = EnumSet.of(TermVectorsRequest.Flag.Positions, TermVectorsRequest.Flag.Offsets); response.setFields(generatedFields, request.selectedFields(), flags, generatedFields); responses[i++] = new MultiTermVectorsItemResponse(response, null); @@ -186,7 +225,12 @@ private static Fields generateFields(String[] fieldNames, String text) throws IO @Override protected void doAssertLuceneQuery(MoreLikeThisQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException { - assertThat(query, anyOf(instanceOf(BooleanQuery.class), instanceOf(MoreLikeThisQuery.class))); + if (!queryBuilder.likeItems().isEmpty()) { + assertThat(query, instanceOf(BooleanQuery.class)); + } else { + // we rely on integration tests for a deeper check here + assertThat(query, instanceOf(MoreLikeThisQuery.class)); + } } @Test @@ -212,25 +256,24 @@ public void testValidateItems() { MoreLikeThisQueryBuilder queryBuilder = new MoreLikeThisQueryBuilder("field").like("some text"); int totalExpectedErrors = 0; if (randomBoolean()) { - queryBuilder.addLikeItem(generateRandomItem().id(null)); + queryBuilder.addLikeItem(generateRandomItem().id(null).doc((XContentBuilder) null)); totalExpectedErrors++; } if (randomBoolean()) { - queryBuilder.addLikeItem(generateRandomItem().id(null).doc((XContentBuilder) null)); + queryBuilder.addUnlikeItem(generateRandomItem().id(null).doc((XContentBuilder) null)); totalExpectedErrors++; } if (randomBoolean()) { - queryBuilder.addUnlikeItem(generateRandomItem().id(null)); + queryBuilder.addLikeItem(generateRandomItem().id("id").doc(new BytesArray(""))); + totalExpectedErrors++; + } + if (randomBoolean()) { + queryBuilder.addUnlikeItem(generateRandomItem().id("id").doc(new BytesArray(""))); totalExpectedErrors++; } assertValidate(queryBuilder, totalExpectedErrors); } - @Test - public void testArtificialDocument() { - - } - @Test public void testUnsupportedFields() throws IOException { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); @@ -258,14 +301,9 @@ public void testItemSerialization() throws IOException { @Test public void testItemFromXContent() throws IOException { Item expectedItem = generateRandomItem(); - String json = expectedItem.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS).string(); + String json = expectedItem.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS).string(); XContentParser parser = XContentFactory.xContent(json).createParser(json); Item newItem = Item.parse(parser, ParseFieldMatcher.STRICT, new Item()); assertEquals(expectedItem, newItem); } - - @Test - public void testPerFieldAnalyzer() { - - } } From 5d765145d9bfa7266cc8fbabd4920d6f8b3dadc4 Mon Sep 17 00:00:00 2001 From: Alex Ksikes Date: Mon, 14 Sep 2015 22:07:52 +0200 Subject: [PATCH 07/10] minor change Item#doc(XContentBuilder) --- .../elasticsearch/index/query/MoreLikeThisQueryBuilder.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java index d50e1c30defd4..97a2cadb0454b 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java @@ -204,11 +204,7 @@ public Item doc(BytesReference doc) { * Sets to a given artificial document, that is a document that is not present in the index. */ public Item doc(XContentBuilder doc) { - if (doc == null) { - this.doc = null; - } else { - this.doc(doc.bytes()); - } + this.doc = doc != null ? doc.bytes() : null; return this; } From c9ffe10bbfd03b5f69ab755b48878e96e581921f Mon Sep 17 00:00:00 2001 From: Alex Ksikes Date: Tue, 15 Sep 2015 18:10:26 +0200 Subject: [PATCH 08/10] addressed comments --- .../common/io/stream/StreamOutput.java | 2 +- .../index/query/MoreLikeThisQueryBuilder.java | 86 +++++++------------ .../index/query/MoreLikeThisQueryParser.java | 4 - .../query/MoreLikeThisQueryBuilderTests.java | 58 +++++-------- .../query/SimpleIndexQueryParserTests.java | 70 +++++++++++++-- 5 files changed, 116 insertions(+), 104 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java index 77f1fb456e547..fe4026e2a5847 100644 --- a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java +++ b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java @@ -317,7 +317,7 @@ public void writeStringArrayNullable(@Nullable String[] array) throws IOExceptio } /** - * Writes a string array, for nullable string, writes it as 0 (empty string). + * Writes a string array, for nullable string, writes false. */ public void writeOptionalStringArray(@Nullable String[] array) throws IOException { if (array == null) { diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java index 97a2cadb0454b..650e3c503fd31 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java @@ -142,9 +142,12 @@ public Item() { * @param type the type of the document * @param id and its id */ - public Item(String index, @Nullable String type, String id) { + public Item(@Nullable String index, @Nullable String type, String id) { this.index = index; this.type = type; + if (id == null) { + throw new IllegalArgumentException("Item requires id to be non-null"); + } this.id = id; } @@ -155,10 +158,13 @@ public Item(String index, @Nullable String type, String id) { * @param type the type to be used for parsing the doc * @param doc the document specification */ - public Item(String index, String type, XContentBuilder doc) { + public Item(@Nullable String index, @Nullable String type, XContentBuilder doc) { this.index = index; this.type = type; - this.doc(doc); + if (doc == null) { + throw new IllegalArgumentException("Item requires doc to be non-null"); + } + this.doc = doc.bytes(); } public String index() { @@ -183,31 +189,10 @@ public String id() { return id; } - public Item id(String id) { - this.id = id; - return this; - } - public BytesReference doc() { return doc; } - /** - * Sets to a given artificial document, that is a document that is not present in the index. - */ - public Item doc(BytesReference doc) { - this.doc = doc; - return this; - } - - /** - * Sets to a given artificial document, that is a document that is not present in the index. - */ - public Item doc(XContentBuilder doc) { - this.doc = doc != null ? doc.bytes() : null; - return this; - } - public String[] fields() { return fields; } @@ -275,7 +260,7 @@ public TermVectorsRequest toTermVectorsRequest() { // for artificial docs to make sure that the id has changed in the item too if (doc != null) { termVectorsRequest.doc(doc, true); - this.id(termVectorsRequest.id()); + this.id = termVectorsRequest.id(); } return termVectorsRequest; } @@ -297,7 +282,7 @@ public static Item parse(XContentParser parser, ParseFieldMatcher parseFieldMatc } else if (parseFieldMatcher.match(currentFieldName, Field.ID)) { item.id = parser.text(); } else if (parseFieldMatcher.match(currentFieldName, Field.DOC)) { - item.doc(jsonBuilder().copyCurrentStructure(parser)); + item.doc = jsonBuilder().copyCurrentStructure(parser).bytes(); } else if (parseFieldMatcher.match(currentFieldName, Field.FIELDS)) { if (token == XContentParser.Token.START_ARRAY) { List fields = new ArrayList<>(); @@ -328,6 +313,10 @@ public static Item parse(XContentParser parser, ParseFieldMatcher parseFieldMatc throw new ElasticsearchParseException( "failed to parse More Like This item. either [id] or [doc] can be specified, but not both!"); } + if (item.id == null && item.doc == null) { + throw new ElasticsearchParseException( + "failed to parse More Like This item. neither [id] nor [doc] is specified!"); + } return item; } @@ -340,7 +329,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (this.type != null) { builder.field(Field.TYPE.getPreferredName(), this.type); } - if (this.id != null && this.doc == null) { + if (this.id != null) { builder.field(Field.ID.getPreferredName(), this.id); } if (this.doc != null) { @@ -384,23 +373,16 @@ public final String toString() { } } - public QueryValidationException validate() { - QueryValidationException validationException = null; - if (id == null && doc == null) { - validationException = new QueryValidationException(); - validationException.addValidationError("item must either have an '_id' or be an artificial 'doc'"); - } - if (id != null && doc != null) { - validationException = validationException == null ? new QueryValidationException() : validationException; - validationException.addValidationError("either 'id' or 'doc' can be specified, but not both!"); - } - return validationException; - } - @Override public Item readFrom(StreamInput in) throws IOException { - Item item = new Item(in.readOptionalString(), in.readOptionalString(), in.readOptionalString()); - item.doc = (BytesReference) in.readGenericValue(); + Item item = new Item(); + item.index = in.readOptionalString(); + item.type = in.readOptionalString(); + if (in.readBoolean()) { + item.doc = (BytesReference) in.readGenericValue(); + } else { + item.id = in.readString(); + } item.fields = in.readOptionalStringArray(); item.perFieldAnalyzer = (Map) in.readGenericValue(); item.routing = in.readOptionalString(); @@ -417,8 +399,12 @@ public static Item readItemFrom(StreamInput in) throws IOException { public void writeTo(StreamOutput out) throws IOException { out.writeOptionalString(index); out.writeOptionalString(type); - out.writeOptionalString(id); - out.writeGenericValue(doc); + out.writeBoolean(doc != null); + if (doc != null) { + out.writeGenericValue(doc); + } else { + out.writeString(id); + } out.writeOptionalStringArray(fields); out.writeGenericValue(perFieldAnalyzer); out.writeOptionalString(routing); @@ -1028,18 +1014,6 @@ public QueryValidationException validate() { if (fields != null && fields.isEmpty()) { validationException = addValidationError("requires 'fields' to be specified", validationException); } - for (Item likeItem : likeItems) { - QueryValidationException validate = likeItem.validate(); - if (validate != null) { - validationException = addValidationError(validate.getMessage(), validationException); - } - } - for (Item unlikeItem : unlikeItems) { - QueryValidationException validate = unlikeItem.validate(); - if (validate != null) { - validationException = addValidationError(validate.getMessage(), validationException); - } - } return validationException; } diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java index 747d2c7b44a3b..821b8a5969f63 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java @@ -55,10 +55,6 @@ public interface Field { ParseField FAIL_ON_UNSUPPORTED_FIELD = new ParseField("fail_on_unsupported_field"); } - public MoreLikeThisQueryParser() { - - } - @Override public String[] names() { return new String[]{MoreLikeThisQueryBuilder.NAME, "more_like_this", "moreLikeThis"}; diff --git a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java index bcb81f9ced50d..589ced4dd357a 100644 --- a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java @@ -29,7 +29,6 @@ import org.elasticsearch.action.termvectors.*; import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.lucene.search.MoreLikeThisQuery; @@ -83,26 +82,32 @@ private static String[] randomStringFields() { } private Item generateRandomItem() { - Item item = new Item() - .index(randomBoolean() ? getIndex().getName() : null) - .type(getRandomType()) // set to one type to avoid ambiguous types - .routing(randomAsciiOfLength(10)) - .version(randomInt(5)) - .versionType(randomFrom(VersionType.values())); - // if no field is specified MLT uses all mapped fields for this item + String index = randomBoolean() ? getIndex().getName() : null; + String type = getRandomType(); // set to one type to avoid ambiguous types + // indexed item or artificial document + Item item; if (randomBoolean()) { - item.fields(randomFrom(randomFields)); + item = new Item(index, type, randomAsciiOfLength(10)); + } else { + item = new Item(index, type, randomArtificialDoc()); } - // id or artificial doc + // if no field is specified MLT uses all mapped fields for this item if (randomBoolean()) { - item.id(randomAsciiOfLength(10)); - } else { - item.doc(randomArtificialDoc()); + item.fields(randomFrom(randomFields)); } // per field analyzer if (randomBoolean()) { item.perFieldAnalyzer(randomPerFieldAnalyzer()); } + if (randomBoolean()) { + item.routing(randomAsciiOfLength(10)); + } + if (randomBoolean()) { + item.version(randomInt(5)); + } + if (randomBoolean()) { + item.versionType(randomFrom(VersionType.values())); + } return item; } @@ -178,10 +183,10 @@ protected MoreLikeThisQueryBuilder doCreateTestQueryBuilder() { queryBuilder.boostTerms(randomFloat() * 10); } if (randomBoolean()) { - queryBuilder.include(true); + queryBuilder.include(randomBoolean()); } if (randomBoolean()) { - queryBuilder.failOnUnsupportedField(false); + queryBuilder.failOnUnsupportedField(randomBoolean()); } return queryBuilder; } @@ -251,29 +256,6 @@ public void testValidate() { assertNull(queryBuilder.validate()); } - @Test - public void testValidateItems() { - MoreLikeThisQueryBuilder queryBuilder = new MoreLikeThisQueryBuilder("field").like("some text"); - int totalExpectedErrors = 0; - if (randomBoolean()) { - queryBuilder.addLikeItem(generateRandomItem().id(null).doc((XContentBuilder) null)); - totalExpectedErrors++; - } - if (randomBoolean()) { - queryBuilder.addUnlikeItem(generateRandomItem().id(null).doc((XContentBuilder) null)); - totalExpectedErrors++; - } - if (randomBoolean()) { - queryBuilder.addLikeItem(generateRandomItem().id("id").doc(new BytesArray(""))); - totalExpectedErrors++; - } - if (randomBoolean()) { - queryBuilder.addUnlikeItem(generateRandomItem().id("id").doc(new BytesArray(""))); - totalExpectedErrors++; - } - assertValidate(queryBuilder, totalExpectedErrors); - } - @Test public void testUnsupportedFields() throws IOException { assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); diff --git a/core/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java b/core/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java index e69d1bbc03ea6..747bc060bc67e 100644 --- a/core/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java @@ -20,14 +20,41 @@ package org.elasticsearch.index.query; import org.apache.lucene.analysis.core.WhitespaceAnalyzer; -import org.apache.lucene.index.*; +import org.apache.lucene.index.Fields; +import org.apache.lucene.index.MultiFields; +import org.apache.lucene.index.Term; +import org.apache.lucene.index.Terms; +import org.apache.lucene.index.TermsEnum; import org.apache.lucene.index.memory.MemoryIndex; import org.apache.lucene.queries.BoostingQuery; import org.apache.lucene.queries.ExtendedCommonTermsQuery; import org.apache.lucene.queries.TermsQuery; -import org.apache.lucene.search.*; +import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanClause.Occur; -import org.apache.lucene.search.spans.*; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.BoostQuery; +import org.apache.lucene.search.ConstantScoreQuery; +import org.apache.lucene.search.DisjunctionMaxQuery; +import org.apache.lucene.search.FuzzyQuery; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.MultiTermQuery; +import org.apache.lucene.search.NumericRangeQuery; +import org.apache.lucene.search.PrefixQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryWrapperFilter; +import org.apache.lucene.search.RegexpQuery; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TermRangeQuery; +import org.apache.lucene.search.WildcardQuery; +import org.apache.lucene.search.spans.FieldMaskingSpanQuery; +import org.apache.lucene.search.spans.SpanContainingQuery; +import org.apache.lucene.search.spans.SpanFirstQuery; +import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper; +import org.apache.lucene.search.spans.SpanNearQuery; +import org.apache.lucene.search.spans.SpanNotQuery; +import org.apache.lucene.search.spans.SpanOrQuery; +import org.apache.lucene.search.spans.SpanTermQuery; +import org.apache.lucene.search.spans.SpanWithinQuery; import org.apache.lucene.spatial.prefix.IntersectsPrefixTreeFilter; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefBuilder; @@ -53,6 +80,7 @@ import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.core.NumberFieldMapper; +import org.elasticsearch.index.query.MoreLikeThisQueryBuilder.Item; import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders; import org.elasticsearch.index.search.geo.GeoDistanceRangeQuery; import org.elasticsearch.index.search.geo.GeoPolygonQuery; @@ -71,11 +99,43 @@ import java.util.concurrent.ExecutionException; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.elasticsearch.index.query.QueryBuilders.*; +import static org.elasticsearch.index.query.QueryBuilders.boolQuery; +import static org.elasticsearch.index.query.QueryBuilders.boostingQuery; +import static org.elasticsearch.index.query.QueryBuilders.commonTermsQuery; +import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery; +import static org.elasticsearch.index.query.QueryBuilders.disMaxQuery; +import static org.elasticsearch.index.query.QueryBuilders.functionScoreQuery; +import static org.elasticsearch.index.query.QueryBuilders.fuzzyQuery; +import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; +import static org.elasticsearch.index.query.QueryBuilders.moreLikeThisQuery; +import static org.elasticsearch.index.query.QueryBuilders.multiMatchQuery; +import static org.elasticsearch.index.query.QueryBuilders.notQuery; +import static org.elasticsearch.index.query.QueryBuilders.prefixQuery; +import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery; +import static org.elasticsearch.index.query.QueryBuilders.rangeQuery; +import static org.elasticsearch.index.query.QueryBuilders.regexpQuery; +import static org.elasticsearch.index.query.QueryBuilders.spanContainingQuery; +import static org.elasticsearch.index.query.QueryBuilders.spanFirstQuery; +import static org.elasticsearch.index.query.QueryBuilders.spanNearQuery; +import static org.elasticsearch.index.query.QueryBuilders.spanNotQuery; +import static org.elasticsearch.index.query.QueryBuilders.spanOrQuery; +import static org.elasticsearch.index.query.QueryBuilders.spanTermQuery; +import static org.elasticsearch.index.query.QueryBuilders.spanWithinQuery; +import static org.elasticsearch.index.query.QueryBuilders.termQuery; +import static org.elasticsearch.index.query.QueryBuilders.termsQuery; +import static org.elasticsearch.index.query.QueryBuilders.wildcardQuery; import static org.elasticsearch.test.StreamsUtils.copyToBytesFromClasspath; import static org.elasticsearch.test.StreamsUtils.copyToStringFromClasspath; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBooleanSubQuery; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; public class SimpleIndexQueryParserTests extends ESSingleNodeTestCase { From bad21641d3af03dbeeb406c59d030636fb302e42 Mon Sep 17 00:00:00 2001 From: Alex Ksikes Date: Tue, 15 Sep 2015 18:14:18 +0200 Subject: [PATCH 09/10] updated migration doc --- docs/reference/migration/migrate_query_refactoring.asciidoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/reference/migration/migrate_query_refactoring.asciidoc b/docs/reference/migration/migrate_query_refactoring.asciidoc index 560de94cd9063..8ce7ffbfb2e3a 100644 --- a/docs/reference/migration/migrate_query_refactoring.asciidoc +++ b/docs/reference/migration/migrate_query_refactoring.asciidoc @@ -88,3 +88,8 @@ makes the type / path parameter mandatory. Moving MatchQueryBuilder.Type and MatchQueryBuilder.ZeroTermsQuery enum to MatchQuery.Type. Also reusing new Operator enum. + +==== MoreLikeThisQueryBuilder + +Removed MoreLikeThisQueryBuilder.Item#id(String id), Item#doc(BytesReference doc), +Item#doc(XContentBuilder doc). Use provided constructors instead. \ No newline at end of file From 102c16b1b43c013a5029e631287dde569f21557c Mon Sep 17 00:00:00 2001 From: Alex Ksikes Date: Wed, 16 Sep 2015 02:04:53 +0200 Subject: [PATCH 10/10] addressed comments --- .../org/elasticsearch/index/VersionType.java | 6 +- .../index/query/MoreLikeThisQueryBuilder.java | 102 ++++-------------- .../index/query/MoreLikeThisQueryParser.java | 7 +- .../query/MoreLikeThisQueryBuilderTests.java | 12 +-- .../search/morelikethis/MoreLikeThisIT.java | 34 +++--- .../ContextAndHeaderTransportIT.java | 2 +- .../migrate_query_refactoring.asciidoc | 10 +- 7 files changed, 60 insertions(+), 113 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/VersionType.java b/core/src/main/java/org/elasticsearch/index/VersionType.java index 6918b92844237..a5d8cae245365 100644 --- a/core/src/main/java/org/elasticsearch/index/VersionType.java +++ b/core/src/main/java/org/elasticsearch/index/VersionType.java @@ -314,7 +314,9 @@ public static VersionType fromValue(byte value) { @Override public VersionType readFrom(StreamInput in) throws IOException { - return VersionType.values()[in.readVInt()]; + int ordinal = in.readVInt(); + assert (ordinal == 0 || ordinal == 1 || ordinal == 2 || ordinal == 3); + return VersionType.values()[ordinal]; } public static VersionType readVersionTypeFrom(StreamInput in) throws IOException { @@ -323,6 +325,6 @@ public static VersionType readVersionTypeFrom(StreamInput in) throws IOException @Override public void writeTo(StreamOutput out) throws IOException { - out.writeVInt(this.ordinal()); + out.writeVInt(ordinal()); } } diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java index 650e3c503fd31..f2c236a42011c 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java @@ -143,11 +143,11 @@ public Item() { * @param id and its id */ public Item(@Nullable String index, @Nullable String type, String id) { - this.index = index; - this.type = type; if (id == null) { throw new IllegalArgumentException("Item requires id to be non-null"); } + this.index = index; + this.type = type; this.id = id; } @@ -159,11 +159,11 @@ public Item(@Nullable String index, @Nullable String type, String id) { * @param doc the document specification */ public Item(@Nullable String index, @Nullable String type, XContentBuilder doc) { - this.index = index; - this.type = type; if (doc == null) { throw new IllegalArgumentException("Item requires doc to be non-null"); } + this.index = index; + this.type = type; this.doc = doc.bytes(); } @@ -448,7 +448,7 @@ public MoreLikeThisQueryBuilder() { * @param fields the field names that will be used when generating the 'More Like This' query. */ public MoreLikeThisQueryBuilder(String... fields) { - this(Arrays.asList(fields)); + this(Collections.unmodifiableList(Arrays.asList(fields))); } /** @@ -470,8 +470,8 @@ public List fields() { * @param likeTexts the text to use when generating the 'More Like This' query. */ public MoreLikeThisQueryBuilder like(String... likeTexts) { - this.likeTexts = new ArrayList<>(); - return addLikeText(likeTexts); + this.likeTexts = Collections.unmodifiableList(Arrays.asList(likeTexts)); + return this; } public List likeTexts() { @@ -484,36 +484,20 @@ public List likeTexts() { * @param likeItems the documents to use when generating the 'More Like This' query. */ public MoreLikeThisQueryBuilder like(Item... likeItems) { - this.likeItems = new ArrayList<>(); - return addLikeItem(likeItems); + this.likeItems = Collections.unmodifiableList(Arrays.asList(likeItems)); + return this; } public List likeItems() { return likeItems; } - /** - * Adds some text to use in order to find documents that are "like" this. - */ - public MoreLikeThisQueryBuilder addLikeText(String... likeTexts) { - Collections.addAll(this.likeTexts, likeTexts); - return this; - } - - /** - * Adds a document to use in order to find documents that are "like" this. - */ - public MoreLikeThisQueryBuilder addLikeItem(Item... likeItems) { - Collections.addAll(this.likeItems, likeItems); - return this; - } - /** * Sets the text from which the terms should not be selected from. */ public MoreLikeThisQueryBuilder unlike(String... unlikeTexts) { - this.unlikeTexts = new ArrayList<>(); - return addUnlikeText(unlikeTexts); + this.unlikeTexts = Collections.unmodifiableList(Arrays.asList(unlikeTexts)); + return this; } public List unlikeTexts() { @@ -524,30 +508,14 @@ public List unlikeTexts() { * Sets the documents from which the terms should not be selected from. */ public MoreLikeThisQueryBuilder unlike(Item... unlikeItems) { - this.unlikeItems = new ArrayList<>(); - return addUnlikeItem(unlikeItems); + this.unlikeItems = Collections.unmodifiableList(Arrays.asList(unlikeItems)); + return this; } public List unlikeItems() { return unlikeItems; } - /** - * Adds some text to use in order to find documents that are "unlike" this. - */ - public MoreLikeThisQueryBuilder addUnlikeText(String... unlikeTexts) { - Collections.addAll(this.unlikeTexts, unlikeTexts); - return this; - } - - /** - * Adds a document to use in order to find documents that are "unlike" this. - */ - public MoreLikeThisQueryBuilder addUnlikeItem(Item... unlikeItems) { - Collections.addAll(this.unlikeItems, unlikeItems); - return this; - } - /** * Sets the maximum number of query terms that will be included in any generated query. * Defaults to 25. @@ -639,7 +607,10 @@ public MoreLikeThisQueryBuilder stopWords(String... stopWords) { } public MoreLikeThisQueryBuilder stopWords(List stopWords) { - this.stopWords = stopWords != null ? stopWords.toArray(new String[stopWords.size()]) : null; + if (stopWords == null) { + throw new IllegalArgumentException("requires stopwords to be non-null"); + } + this.stopWords = stopWords.toArray(new String[stopWords.size()]); return this; } @@ -730,39 +701,6 @@ public MoreLikeThisQueryBuilder ids(String... ids) { return like(items); } - @Deprecated - public MoreLikeThisQueryBuilder docs(Item... docs) { - return like(docs); - } - - /** - * Sets the documents from which the terms should not be selected from. - * - * @Deprecated Use {@link #unlike(Item...)} instead - */ - @Deprecated - public MoreLikeThisQueryBuilder ignoreLike(Item... docs) { - return unlike(docs); - } - - /** - * Sets the text from which the terms should not be selected from. - * - * @Deprecated Use {@link #unlike(String...)} instead. - */ - @Deprecated - public MoreLikeThisQueryBuilder ignoreLike(String... likeText) { - return unlike(likeText); - } - - /** - * Adds a document to use in order to find documents that are "like" this. - */ - @Deprecated - public MoreLikeThisQueryBuilder addItem(Item... likeItems) { - return addLikeItem(likeItems); - } - @Override protected void doXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(NAME); @@ -861,15 +799,15 @@ protected Query doToQuery(QueryShardContext context) throws IOException { mltQuery.setMoreLikeFields(moreLikeFields.toArray(Strings.EMPTY_ARRAY)); // handle like texts - if (!likeTexts.isEmpty()) { + if (likeTexts.isEmpty() == false) { mltQuery.setLikeText(likeTexts); } - if (!unlikeTexts.isEmpty()) { + if (unlikeTexts.isEmpty() == false) { mltQuery.setUnlikeText(unlikeTexts); } // handle items - if (!likeItems.isEmpty()) { + if (likeItems.isEmpty() == false) { return handleItems(context, mltQuery, likeItems, unlikeItems, include, moreLikeFields, useDefaultField); } else { return mltQuery; diff --git a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java index 821b8a5969f63..97e9084ae0cb8 100644 --- a/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryParser.java @@ -186,7 +186,7 @@ public MoreLikeThisQueryBuilder fromXContent(QueryParseContext parseContext) thr throw new QueryParsingException(parseContext, "more_like_this requires 'fields' to be non-empty"); } - return new MoreLikeThisQueryBuilder(fields) + MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = new MoreLikeThisQueryBuilder(fields) .like(likeTexts.toArray(new String[likeTexts.size()])) .unlike(unlikeTexts.toArray(new String[unlikeTexts.size()])) .like(likeItems.toArray(new Item[likeItems.size()])) @@ -197,7 +197,6 @@ public MoreLikeThisQueryBuilder fromXContent(QueryParseContext parseContext) thr .maxDocFreq(maxDocFreq) .minWordLength(minWordLength) .maxWordLength(maxWordLength) - .stopWords(stopWords) .analyzer(analyzer) .minimumShouldMatch(minimumShouldMatch) .boostTerms(boostTerms) @@ -205,6 +204,10 @@ public MoreLikeThisQueryBuilder fromXContent(QueryParseContext parseContext) thr .failOnUnsupportedField(failOnUnsupportedField) .boost(boost) .queryName(queryName); + if (stopWords != null) { + moreLikeThisQueryBuilder.stopWords(stopWords); + } + return moreLikeThisQueryBuilder; } private static void parseLikeField(QueryParseContext parseContext, List texts, List items) throws IOException { diff --git a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java index 589ced4dd357a..6eb53ace9f955 100644 --- a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java @@ -49,8 +49,6 @@ import java.util.Map; import java.util.stream.Stream; -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; public class MoreLikeThisQueryBuilderTests extends AbstractQueryTestCase { @@ -114,7 +112,7 @@ private Item generateRandomItem() { private XContentBuilder randomArtificialDoc() { XContentBuilder doc; try { - doc = jsonBuilder().startObject(); + doc = XContentFactory.jsonBuilder().startObject(); for (String field : randomFields) { doc.field(field, randomAsciiOfLength(10)); } @@ -171,7 +169,7 @@ protected MoreLikeThisQueryBuilder doCreateTestQueryBuilder() { queryBuilder.maxWordLength(randomInt(25)); } if (randomBoolean()) { - queryBuilder.stopWords(generateRandomStringArray(5, 5, false)); + queryBuilder.stopWords(generateRandomStringArray(5, 5, false, false)); } if (randomBoolean()) { queryBuilder.analyzer(randomAnalyzer()); // fix the analyzer? @@ -231,10 +229,10 @@ private static Fields generateFields(String[] fieldNames, String text) throws IO @Override protected void doAssertLuceneQuery(MoreLikeThisQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException { if (!queryBuilder.likeItems().isEmpty()) { - assertThat(query, instanceOf(BooleanQuery.class)); + assertThat(query, Matchers.instanceOf(BooleanQuery.class)); } else { // we rely on integration tests for a deeper check here - assertThat(query, instanceOf(MoreLikeThisQuery.class)); + assertThat(query, Matchers.instanceOf(MoreLikeThisQuery.class)); } } @@ -283,7 +281,7 @@ public void testItemSerialization() throws IOException { @Test public void testItemFromXContent() throws IOException { Item expectedItem = generateRandomItem(); - String json = expectedItem.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS).string(); + String json = expectedItem.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS).string(); XContentParser parser = XContentFactory.xContent(json).createParser(json); Item newItem = Item.parse(parser, ParseFieldMatcher.STRICT, new Item()); assertEquals(expectedItem, newItem); diff --git a/core/src/test/java/org/elasticsearch/search/morelikethis/MoreLikeThisIT.java b/core/src/test/java/org/elasticsearch/search/morelikethis/MoreLikeThisIT.java index bbc992f75ae56..dc98d0d931255 100644 --- a/core/src/test/java/org/elasticsearch/search/morelikethis/MoreLikeThisIT.java +++ b/core/src/test/java/org/elasticsearch/search/morelikethis/MoreLikeThisIT.java @@ -72,7 +72,7 @@ public void testSimpleMoreLikeThis() throws Exception { logger.info("Running moreLikeThis"); SearchResponse response = client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); + new MoreLikeThisQueryBuilder().like(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); assertHitCount(response, 1l); } @@ -92,7 +92,7 @@ public void testSimpleMoreLikeOnLongField() throws Exception { logger.info("Running moreLikeThis"); SearchResponse response = client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); + new MoreLikeThisQueryBuilder().like(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); assertHitCount(response, 0l); } @@ -119,24 +119,24 @@ public void testMoreLikeThisWithAliases() throws Exception { logger.info("Running moreLikeThis on index"); SearchResponse response = client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); + new MoreLikeThisQueryBuilder().like(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); assertHitCount(response, 2l); logger.info("Running moreLikeThis on beta shard"); response = client().prepareSearch("beta").setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); + new MoreLikeThisQueryBuilder().like(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); assertHitCount(response, 1l); assertThat(response.getHits().getAt(0).id(), equalTo("3")); logger.info("Running moreLikeThis on release shard"); response = client().prepareSearch("release").setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); + new MoreLikeThisQueryBuilder().like(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); assertHitCount(response, 1l); assertThat(response.getHits().getAt(0).id(), equalTo("2")); logger.info("Running moreLikeThis on alias with node client"); response = internalCluster().clientNodeClient().prepareSearch("beta").setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); + new MoreLikeThisQueryBuilder().like(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1)).get(); assertHitCount(response, 1l); assertThat(response.getHits().getAt(0).id(), equalTo("3")); } @@ -156,11 +156,11 @@ public void testMoreLikeThisIssue2197() throws Exception { assertThat(ensureGreen(), equalTo(ClusterHealthStatus.GREEN)); SearchResponse response = client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("foo", "bar", "1"))).get(); + new MoreLikeThisQueryBuilder().like(new Item("foo", "bar", "1"))).get(); assertNoFailures(response); assertThat(response, notNullValue()); response = client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("foo", "bar", "1"))).get(); + new MoreLikeThisQueryBuilder().like(new Item("foo", "bar", "1"))).get(); assertNoFailures(response); assertThat(response, notNullValue()); } @@ -182,7 +182,7 @@ public void testMoreLikeWithCustomRouting() throws Exception { client().admin().indices().prepareRefresh("foo").execute().actionGet(); SearchResponse response = client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("foo", "bar", "1").routing("2"))).get(); + new MoreLikeThisQueryBuilder().like(new Item("foo", "bar", "1").routing("2"))).get(); assertNoFailures(response); assertThat(response, notNullValue()); } @@ -205,7 +205,7 @@ public void testMoreLikeThisIssueRoutingNotSerialized() throws Exception { .execute().actionGet(); client().admin().indices().prepareRefresh("foo").execute().actionGet(); SearchResponse response = client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("foo", "bar", "1").routing("4000"))).get(); + new MoreLikeThisQueryBuilder().like(new Item("foo", "bar", "1").routing("4000"))).get(); assertNoFailures(response); assertThat(response, notNullValue()); } @@ -233,12 +233,12 @@ public void testNumericField() throws Exception { // Implicit list of fields -> ignore numeric fields SearchResponse searchResponse = client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("test", "type", "1")).minTermFreq(1).minDocFreq(1)).get(); + new MoreLikeThisQueryBuilder().like(new Item("test", "type", "1")).minTermFreq(1).minDocFreq(1)).get(); assertHitCount(searchResponse, 1l); // Explicit list of fields including numeric fields -> fail assertThrows(client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder("string_value", "int_value").addLikeItem(new Item("test", "type", "1")).minTermFreq(1).minDocFreq(1)), SearchPhaseExecutionException.class); + new MoreLikeThisQueryBuilder("string_value", "int_value").like(new Item("test", "type", "1")).minTermFreq(1).minDocFreq(1)), SearchPhaseExecutionException.class); // mlt query with no field -> OK searchResponse = client().prepareSearch().setQuery(moreLikeThisQuery().likeText("index").minTermFreq(1).minDocFreq(1)).execute().actionGet(); @@ -295,16 +295,16 @@ public void testSimpleMoreLikeInclude() throws Exception { logger.info("Running More Like This with include true"); SearchResponse response = client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1).include(true).minimumShouldMatch("0%")).get(); + new MoreLikeThisQueryBuilder().like(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1).include(true).minimumShouldMatch("0%")).get(); assertOrderedSearchHits(response, "1", "2"); response = client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("test", "type1", "2")).minTermFreq(1).minDocFreq(1).include(true).minimumShouldMatch("0%")).get(); + new MoreLikeThisQueryBuilder().like(new Item("test", "type1", "2")).minTermFreq(1).minDocFreq(1).include(true).minimumShouldMatch("0%")).get(); assertOrderedSearchHits(response, "2", "1"); logger.info("Running More Like This with include false"); response = client().prepareSearch().setQuery( - new MoreLikeThisQueryBuilder().addLikeItem(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1).minimumShouldMatch("0%")).get(); + new MoreLikeThisQueryBuilder().like(new Item("test", "type1", "1")).minTermFreq(1).minDocFreq(1).minimumShouldMatch("0%")).get(); assertSearchHits(response, "2"); } @@ -355,7 +355,7 @@ public void testSimpleMoreLikeThisIdsMultipleTypes() throws Exception { logger.info("Running MoreLikeThis"); MoreLikeThisQueryBuilder queryBuilder = QueryBuilders.moreLikeThisQuery("text").include(true).minTermFreq(1).minDocFreq(1) - .addLikeItem(new Item("test", "type0", "0")); + .like(new Item("test", "type0", "0")); String[] types = new String[numOfTypes]; for (int i = 0; i < numOfTypes; i++) { @@ -573,7 +573,7 @@ public void testMoreLikeThisUnlike() throws ExecutionException, InterruptedExcep docs.add(new Item("test", "type1", i+"")); mltQuery = moreLikeThisQuery() .like(new Item("test", "type1", doc)) - .ignoreLike(docs.toArray(Item.EMPTY_ARRAY)) + .unlike(docs.toArray(Item.EMPTY_ARRAY)) .minTermFreq(0) .minDocFreq(0) .maxQueryTerms(100) diff --git a/core/src/test/java/org/elasticsearch/transport/ContextAndHeaderTransportIT.java b/core/src/test/java/org/elasticsearch/transport/ContextAndHeaderTransportIT.java index fd805bd66ab2a..e42adc80c485f 100644 --- a/core/src/test/java/org/elasticsearch/transport/ContextAndHeaderTransportIT.java +++ b/core/src/test/java/org/elasticsearch/transport/ContextAndHeaderTransportIT.java @@ -230,7 +230,7 @@ public void testThatMoreLikeThisQueryMultiTermVectorRequestContainsContextAndHea transportClient().admin().indices().prepareRefresh(lookupIndex, queryIndex).get(); MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = QueryBuilders.moreLikeThisQuery("name") - .addLikeItem(new Item(lookupIndex, "type", "1")) + .like(new Item(lookupIndex, "type", "1")) .minTermFreq(1) .minDocFreq(1); diff --git a/docs/reference/migration/migrate_query_refactoring.asciidoc b/docs/reference/migration/migrate_query_refactoring.asciidoc index 8ce7ffbfb2e3a..6fe987c3c9442 100644 --- a/docs/reference/migration/migrate_query_refactoring.asciidoc +++ b/docs/reference/migration/migrate_query_refactoring.asciidoc @@ -91,5 +91,11 @@ Also reusing new Operator enum. ==== MoreLikeThisQueryBuilder -Removed MoreLikeThisQueryBuilder.Item#id(String id), Item#doc(BytesReference doc), -Item#doc(XContentBuilder doc). Use provided constructors instead. \ No newline at end of file +Removed `MoreLikeThisQueryBuilder.Item#id(String id)`, `Item#doc(BytesReference doc)`, +`Item#doc(XContentBuilder doc)`. Use provided constructors instead. + +Removed `MoreLikeThisQueryBuilder#addLike` and `addUnlike` in favor to using the `like` +and `unlike` methods. + +The deprecated `docs(Item... docs)`, `ignoreLike(Item... docs)`, +`ignoreLike(String... likeText)`, `addItem(Item... likeItems)` have been removed.