/*
* 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.builder;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.RandomQueryBuilder;
import org.elasticsearch.search.AbstractSearchTestCase;
import org.elasticsearch.search.rescore.QueryRescorerBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.ScoreSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.EqualsHashCodeTestUtils;
import java.io.IOException;
import java.util.Map;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasToString;
public class SearchSourceBuilderTests extends AbstractSearchTestCase {
public void testFromXContent() throws IOException {
SearchSourceBuilder testSearchSourceBuilder = createSearchSourceBuilder();
XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values()));
if (randomBoolean()) {
builder.prettyPrint();
}
testSearchSourceBuilder.toXContent(builder, ToXContent.EMPTY_PARAMS);
assertParseSearchSource(testSearchSourceBuilder, createParser(builder));
}
private void assertParseSearchSource(SearchSourceBuilder testBuilder, XContentParser parser) throws IOException {
QueryParseContext parseContext = new QueryParseContext(parser);
if (randomBoolean()) {
parser.nextToken(); // sometimes we move it on the START_OBJECT to
// test the embedded case
}
SearchSourceBuilder newBuilder = SearchSourceBuilder.fromXContent(parseContext);
assertNull(parser.nextToken());
assertEquals(testBuilder, newBuilder);
assertEquals(testBuilder.hashCode(), newBuilder.hashCode());
}
private QueryParseContext createParseContext(XContentParser parser) {
return new QueryParseContext(parser);
}
public void testSerialization() throws IOException {
SearchSourceBuilder testBuilder = createSearchSourceBuilder();
try (BytesStreamOutput output = new BytesStreamOutput()) {
testBuilder.writeTo(output);
try (StreamInput in = new NamedWriteableAwareStreamInput(output.bytes().streamInput(), namedWriteableRegistry)) {
SearchSourceBuilder deserializedBuilder = new SearchSourceBuilder(in);
assertEquals(deserializedBuilder, testBuilder);
assertEquals(deserializedBuilder.hashCode(), testBuilder.hashCode());
assertNotSame(deserializedBuilder, testBuilder);
}
}
}
public void testEqualsAndHashcode() throws IOException {
// TODO add test checking that changing any member of this class produces an object that is not equal to the original
EqualsHashCodeTestUtils.checkEqualsAndHashCode(createSearchSourceBuilder(), this::copyBuilder);
}
//we use the streaming infra to create a copy of the builder provided as argument
private SearchSourceBuilder copyBuilder(SearchSourceBuilder original) throws IOException {
return ESTestCase.copyWriteable(original, namedWriteableRegistry, SearchSourceBuilder::new);
}
public void testParseIncludeExclude() throws IOException {
{
String restContent = " { \"_source\": { \"includes\": \"include\", \"excludes\": \"*.field2\"}}";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.fromXContent(createParseContext(parser));
assertArrayEquals(new String[]{"*.field2"}, searchSourceBuilder.fetchSource().excludes());
assertArrayEquals(new String[]{"include"}, searchSourceBuilder.fetchSource().includes());
}
}
{
String restContent = " { \"_source\": false}";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.fromXContent(createParseContext(parser));
assertArrayEquals(new String[]{}, searchSourceBuilder.fetchSource().excludes());
assertArrayEquals(new String[]{}, searchSourceBuilder.fetchSource().includes());
assertFalse(searchSourceBuilder.fetchSource().fetchSource());
}
}
}
public void testMultipleQueryObjectsAreRejected() throws Exception {
String restContent =
" { \"query\": {\n" +
" \"multi_match\": {\n" +
" \"query\": \"workd\",\n" +
" \"fields\": [\"title^5\", \"plain_body\"]\n" +
" },\n" +
" \"filters\": {\n" +
" \"terms\": {\n" +
" \"status\": [ 3 ]\n" +
" }\n" +
" }\n" +
" } }";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
ParsingException e = expectThrows(ParsingException.class, () -> SearchSourceBuilder.fromXContent(createParseContext(parser)));
assertEquals("[multi_match] malformed query, expected [END_OBJECT] but found [FIELD_NAME]", e.getMessage());
}
}
public void testParseSort() throws IOException {
{
String restContent = " { \"sort\": \"foo\"}";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.fromXContent(createParseContext(parser));
assertEquals(1, searchSourceBuilder.sorts().size());
assertEquals(new FieldSortBuilder("foo"), searchSourceBuilder.sorts().get(0));
}
}
{
String restContent = "{\"sort\" : [\n" +
" { \"post_date\" : {\"order\" : \"asc\"}},\n" +
" \"user\",\n" +
" { \"name\" : \"desc\" },\n" +
" { \"age\" : \"desc\" },\n" +
" \"_score\"\n" +
" ]}";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.fromXContent(createParseContext(parser));
assertEquals(5, searchSourceBuilder.sorts().size());
assertEquals(new FieldSortBuilder("post_date"), searchSourceBuilder.sorts().get(0));
assertEquals(new FieldSortBuilder("user"), searchSourceBuilder.sorts().get(1));
assertEquals(new FieldSortBuilder("name").order(SortOrder.DESC), searchSourceBuilder.sorts().get(2));
assertEquals(new FieldSortBuilder("age").order(SortOrder.DESC), searchSourceBuilder.sorts().get(3));
assertEquals(new ScoreSortBuilder(), searchSourceBuilder.sorts().get(4));
}
}
}
public void testAggsParsing() throws IOException {
{
String restContent = "{\n" + " " +
"\"aggs\": {" +
" \"test_agg\": {\n" +
" " + "\"terms\" : {\n" +
" \"field\": \"foo\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}\n";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.fromXContent(createParseContext(parser));
assertEquals(1, searchSourceBuilder.aggregations().count());
}
}
{
String restContent = "{\n" +
" \"aggregations\": {" +
" \"test_agg\": {\n" +
" \"terms\" : {\n" +
" \"field\": \"foo\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}\n";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.fromXContent(createParseContext(parser));
assertEquals(1, searchSourceBuilder.aggregations().count());
}
}
}
/**
* test that we can parse the `rescore` element either as single object or as array
*/
public void testParseRescore() throws IOException {
{
String restContent = "{\n" +
" \"query\" : {\n" +
" \"match\": { \"content\": { \"query\": \"foo bar\" }}\n" +
" },\n" +
" \"rescore\": {" +
" \"window_size\": 50,\n" +
" \"query\": {\n" +
" \"rescore_query\" : {\n" +
" \"match\": { \"content\": { \"query\": \"baz\" } }\n" +
" }\n" +
" }\n" +
" }\n" +
"}\n";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.fromXContent(createParseContext(parser));
assertEquals(1, searchSourceBuilder.rescores().size());
assertEquals(new QueryRescorerBuilder(QueryBuilders.matchQuery("content", "baz")).windowSize(50),
searchSourceBuilder.rescores().get(0));
}
}
{
String restContent = "{\n" +
" \"query\" : {\n" +
" \"match\": { \"content\": { \"query\": \"foo bar\" }}\n" +
" },\n" +
" \"rescore\": [ {" +
" \"window_size\": 50,\n" +
" \"query\": {\n" +
" \"rescore_query\" : {\n" +
" \"match\": { \"content\": { \"query\": \"baz\" } }\n" +
" }\n" +
" }\n" +
" } ]\n" +
"}\n";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.fromXContent(createParseContext(parser));
assertEquals(1, searchSourceBuilder.rescores().size());
assertEquals(new QueryRescorerBuilder(QueryBuilders.matchQuery("content", "baz")).windowSize(50),
searchSourceBuilder.rescores().get(0));
}
}
}
public void testTimeoutWithUnits() throws IOException {
final String timeout = randomTimeValue();
final String query = "{ \"query\": { \"match_all\": {}}, \"timeout\": \"" + timeout + "\"}";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, query)) {
final SearchSourceBuilder builder = SearchSourceBuilder.fromXContent(createParseContext(parser));
assertThat(builder.timeout(), equalTo(TimeValue.parseTimeValue(timeout, null, "timeout")));
}
}
public void testTimeoutWithoutUnits() throws IOException {
final int timeout = randomIntBetween(1, 1024);
final String query = "{ \"query\": { \"match_all\": {}}, \"timeout\": \"" + timeout + "\"}";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, query)) {
final ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, () -> SearchSourceBuilder.fromXContent(
createParseContext(parser)));
assertThat(e, hasToString(containsString("unit is missing or unrecognized")));
}
}
public void testToXContent() throws IOException {
//verify that only what is set gets printed out through toXContent
XContentType xContentType = randomFrom(XContentType.values());
{
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
XContentBuilder builder = XContentFactory.contentBuilder(xContentType);
searchSourceBuilder.toXContent(builder, ToXContent.EMPTY_PARAMS);
BytesReference source = builder.bytes();
Map<String, Object> sourceAsMap = XContentHelper.convertToMap(source, false, xContentType).v2();
assertEquals(0, sourceAsMap.size());
}
{
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(RandomQueryBuilder.createQuery(random()));
XContentBuilder builder = XContentFactory.contentBuilder(xContentType);
searchSourceBuilder.toXContent(builder, ToXContent.EMPTY_PARAMS);
BytesReference source = builder.bytes();
Map<String, Object> sourceAsMap = XContentHelper.convertToMap(source, false, xContentType).v2();
assertEquals(1, sourceAsMap.size());
assertEquals("query", sourceAsMap.keySet().iterator().next());
}
}
public void testParseIndicesBoost() throws IOException {
{
String restContent = " { \"indices_boost\": {\"foo\": 1.0, \"bar\": 2.0}}";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.fromXContent(createParseContext(parser));
assertEquals(2, searchSourceBuilder.indexBoosts().size());
assertEquals(new SearchSourceBuilder.IndexBoost("foo", 1.0f), searchSourceBuilder.indexBoosts().get(0));
assertEquals(new SearchSourceBuilder.IndexBoost("bar", 2.0f), searchSourceBuilder.indexBoosts().get(1));
assertWarnings("Object format in indices_boost is deprecated, please use array format instead");
}
}
{
String restContent = "{" +
" \"indices_boost\" : [\n" +
" { \"foo\" : 1.0 },\n" +
" { \"bar\" : 2.0 },\n" +
" { \"baz\" : 3.0 }\n" +
" ]}";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.fromXContent(createParseContext(parser));
assertEquals(3, searchSourceBuilder.indexBoosts().size());
assertEquals(new SearchSourceBuilder.IndexBoost("foo", 1.0f), searchSourceBuilder.indexBoosts().get(0));
assertEquals(new SearchSourceBuilder.IndexBoost("bar", 2.0f), searchSourceBuilder.indexBoosts().get(1));
assertEquals(new SearchSourceBuilder.IndexBoost("baz", 3.0f), searchSourceBuilder.indexBoosts().get(2));
}
}
{
String restContent = "{" +
" \"indices_boost\" : [\n" +
" { \"foo\" : 1.0, \"bar\": 2.0}\n" + // invalid format
" ]}";
assertIndicesBoostParseErrorMessage(restContent, "Expected [END_OBJECT] in [indices_boost] but found [FIELD_NAME]");
}
{
String restContent = "{" +
" \"indices_boost\" : [\n" +
" {}\n" + // invalid format
" ]}";
assertIndicesBoostParseErrorMessage(restContent, "Expected [FIELD_NAME] in [indices_boost] but found [END_OBJECT]");
}
{
String restContent = "{" +
" \"indices_boost\" : [\n" +
" { \"foo\" : \"bar\"}\n" + // invalid format
" ]}";
assertIndicesBoostParseErrorMessage(restContent, "Expected [VALUE_NUMBER] in [indices_boost] but found [VALUE_STRING]");
}
{
String restContent = "{" +
" \"indices_boost\" : [\n" +
" { \"foo\" : {\"bar\": 1}}\n" + // invalid format
" ]}";
assertIndicesBoostParseErrorMessage(restContent, "Expected [VALUE_NUMBER] in [indices_boost] but found [START_OBJECT]");
}
}
public void testNegativeFromErrors() {
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> new SearchSourceBuilder().from(-2));
assertEquals("[from] parameter cannot be negative", expected.getMessage());
}
private void assertIndicesBoostParseErrorMessage(String restContent, String expectedErrorMessage) throws IOException {
try (XContentParser parser = createParser(JsonXContent.jsonXContent, restContent)) {
ParsingException e = expectThrows(ParsingException.class, () -> SearchSourceBuilder.fromXContent(createParseContext(parser)));
assertEquals(expectedErrorMessage, e.getMessage());
}
}
}