Completion Suggester: Fix CompletionFieldMapper to correctly parse weight

- Allows weight to be defined as a string representation of a positive integer

closes #8090
This commit is contained in:
Areek Zillur 2014-10-22 13:44:33 -04:00
parent 462336c135
commit 96f1606cdc
3 changed files with 83 additions and 6 deletions

View File

@ -119,8 +119,9 @@ The following parameters are supported:
might not yield any results, if `input` and `output` differ strongly). might not yield any results, if `input` and `output` differ strongly).
`weight`:: `weight`::
A positive integer, which defines a weight and allows you to A positive integer or a string containing a positive integer,
rank your suggestions. This field is optional. which defines a weight and allows you to rank your suggestions.
This field is optional.
NOTE: Even though you will lose most of the features of the NOTE: Even though you will lose most of the features of the
completion suggest, you can choose to use the following shorthand form. completion suggest, you can choose to use the following shorthand form.

View File

@ -295,16 +295,24 @@ public class CompletionFieldMapper extends AbstractFieldMapper<String> {
if (Fields.CONTENT_FIELD_NAME_INPUT.equals(currentFieldName)) { if (Fields.CONTENT_FIELD_NAME_INPUT.equals(currentFieldName)) {
inputs.add(parser.text()); inputs.add(parser.text());
} }
if (Fields.CONTENT_FIELD_NAME_WEIGHT.equals(currentFieldName)) {
Number weightValue;
try {
weightValue = Long.parseLong(parser.text());
} catch (NumberFormatException e) {
throw new ElasticsearchIllegalArgumentException("Weight must be a string representing a numeric value, but was [" + parser.text() + "]");
}
weight = weightValue.longValue(); // always parse a long to make sure we don't get overflow
checkWeight(weight);
}
} else if (token == XContentParser.Token.VALUE_NUMBER) { } else if (token == XContentParser.Token.VALUE_NUMBER) {
if (Fields.CONTENT_FIELD_NAME_WEIGHT.equals(currentFieldName)) { if (Fields.CONTENT_FIELD_NAME_WEIGHT.equals(currentFieldName)) {
NumberType numberType = parser.numberType(); NumberType numberType = parser.numberType();
if (NumberType.LONG != numberType && NumberType.INT != numberType) { if (NumberType.LONG != numberType && NumberType.INT != numberType) {
throw new ElasticsearchIllegalArgumentException("Weight must be an integer, but was [" + parser.numberValue() + "]"); throw new ElasticsearchIllegalArgumentException("Weight must be an integer, but was [" + parser.numberValue() + "]");
} }
weight = parser.longValue(); // always parse a long to make sure we don't get the overflow value weight = parser.longValue(); // always parse a long to make sure we don't get overflow
if (weight < 0 || weight > Integer.MAX_VALUE) { checkWeight(weight);
throw new ElasticsearchIllegalArgumentException("Weight must be in the interval [0..2147483647], but was [" + weight + "]");
}
} }
} else if (token == XContentParser.Token.START_ARRAY) { } else if (token == XContentParser.Token.START_ARRAY) {
if (Fields.CONTENT_FIELD_NAME_INPUT.equals(currentFieldName)) { if (Fields.CONTENT_FIELD_NAME_INPUT.equals(currentFieldName)) {
@ -341,6 +349,12 @@ public class CompletionFieldMapper extends AbstractFieldMapper<String> {
} }
} }
private void checkWeight(long weight) {
if (weight < 0 || weight > Integer.MAX_VALUE) {
throw new ElasticsearchIllegalArgumentException("Weight must be in the interval [0..2147483647], but was [" + weight + "]");
}
}
/** /**
* Get the context mapping associated with this completion field. * Get the context mapping associated with this completion field.
*/ */

View File

@ -178,6 +178,68 @@ public class CompletionSuggestSearchTests extends ElasticsearchIntegrationTest {
} }
} }
@Test
public void testThatWeightCanBeAString() throws Exception {
createIndexAndMapping(completionMappingBuilder);
client().prepareIndex(INDEX, TYPE, "1").setSource(jsonBuilder()
.startObject().startObject(FIELD)
.startArray("input").value("testing").endArray()
.field("weight", "10")
.endObject().endObject()
).get();
refresh();
SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(
new CompletionSuggestionBuilder("testSuggestions").field(FIELD).text("test").size(10)
).execute().actionGet();
assertSuggestions(suggestResponse, "testSuggestions", "testing");
Suggest.Suggestion.Entry.Option option = suggestResponse.getSuggest().getSuggestion("testSuggestions").getEntries().get(0).getOptions().get(0);
assertThat(option, is(instanceOf(CompletionSuggestion.Entry.Option.class)));
CompletionSuggestion.Entry.Option prefixOption = (CompletionSuggestion.Entry.Option) option;
assertThat(prefixOption.getText().string(), equalTo("testing"));
assertThat((long) prefixOption.getScore(), equalTo(10l));
}
@Test
public void testThatWeightMustNotBeANonNumberString() throws Exception {
createIndexAndMapping(completionMappingBuilder);
try {
client().prepareIndex(INDEX, TYPE, "1").setSource(jsonBuilder()
.startObject().startObject(FIELD)
.startArray("input").value("sth").endArray()
.field("weight", "thisIsNotValid")
.endObject().endObject()
).get();
fail("Indexing with a non-number representing string as weight was successful, but should not be");
} catch (MapperParsingException e) {
assertThat(ExceptionsHelper.detailedMessage(e), containsString("thisIsNotValid"));
}
}
@Test
public void testThatWeightAsStringMustBeInt() throws Exception {
createIndexAndMapping(completionMappingBuilder);
String weight = String.valueOf(Long.MAX_VALUE - 4);
try {
client().prepareIndex(INDEX, TYPE, "1").setSource(jsonBuilder()
.startObject().startObject(FIELD)
.startArray("input").value("testing").endArray()
.field("weight", weight)
.endObject().endObject()
).get();
fail("Indexing with weight string representing value > Int.MAX_VALUE was successful, but should not be");
} catch (MapperParsingException e) {
assertThat(ExceptionsHelper.detailedMessage(e), containsString(weight));
}
}
@Test @Test
public void testThatInputCanBeAStringInsteadOfAnArray() throws Exception { public void testThatInputCanBeAStringInsteadOfAnArray() throws Exception {
createIndexAndMapping(completionMappingBuilder); createIndexAndMapping(completionMappingBuilder);