/* * 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; import org.apache.lucene.search.Explanation; import org.elasticsearch.action.OriginalIndices; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.InputStreamStreamInput; import org.elasticsearch.common.text.Text; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.Index; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.search.SearchHit.NestedIdentity; import org.elasticsearch.search.fetch.subphase.highlight.HighlightField; import org.elasticsearch.search.fetch.subphase.highlight.HighlightFieldTests; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.RandomObjects; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import static org.elasticsearch.common.xcontent.XContentHelper.toXContent; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; public class SearchHitTests extends ESTestCase { private static Set<String> META_FIELDS = Sets.newHashSet("_uid", "_all", "_parent", "_routing", "_size", "_timestamp", "_ttl"); public static SearchHit createTestItem(boolean withOptionalInnerHits) { int internalId = randomInt(); String uid = randomAlphaOfLength(10); Text type = new Text(randomAlphaOfLengthBetween(5, 10)); NestedIdentity nestedIdentity = null; if (randomBoolean()) { nestedIdentity = NestedIdentityTests.createTestItem(randomIntBetween(0, 2)); } Map<String, SearchHitField> fields = new HashMap<>(); if (randomBoolean()) { int size = randomIntBetween(0, 10); for (int i = 0; i < size; i++) { Tuple<List<Object>, List<Object>> values = RandomObjects.randomStoredFieldValues(random(), XContentType.JSON); if (randomBoolean()) { String metaField = randomFrom(META_FIELDS); fields.put(metaField, new SearchHitField(metaField, values.v1())); } else { String fieldName = randomAlphaOfLengthBetween(5, 10); fields.put(fieldName, new SearchHitField(fieldName, values.v1())); } } } SearchHit hit = new SearchHit(internalId, uid, type, nestedIdentity, fields); if (frequently()) { if (rarely()) { hit.score(Float.NaN); } else { hit.score(randomFloat()); } } if (frequently()) { hit.sourceRef(RandomObjects.randomSource(random())); } if (randomBoolean()) { hit.version(randomLong()); } if (randomBoolean()) { hit.sortValues(SearchSortValuesTests.createTestItem()); } if (randomBoolean()) { int size = randomIntBetween(0, 5); Map<String, HighlightField> highlightFields = new HashMap<>(size); for (int i = 0; i < size; i++) { highlightFields.put(randomAlphaOfLength(5), HighlightFieldTests.createTestItem()); } hit.highlightFields(highlightFields); } if (randomBoolean()) { int size = randomIntBetween(0, 5); String[] matchedQueries = new String[size]; for (int i = 0; i < size; i++) { matchedQueries[i] = randomAlphaOfLength(5); } hit.matchedQueries(matchedQueries); } if (randomBoolean()) { hit.explanation(createExplanation(randomIntBetween(0, 5))); } if (withOptionalInnerHits) { int innerHitsSize = randomIntBetween(0, 3); Map<String, SearchHits> innerHits = new HashMap<>(innerHitsSize); for (int i = 0; i < innerHitsSize; i++) { innerHits.put(randomAlphaOfLength(5), SearchHitsTests.createTestItem()); } hit.setInnerHits(innerHits); } if (randomBoolean()) { hit.shard(new SearchShardTarget(randomAlphaOfLengthBetween(5, 10), new ShardId(new Index(randomAlphaOfLengthBetween(5, 10), randomAlphaOfLengthBetween(5, 10)), randomInt()), null, OriginalIndices.NONE)); } return hit; } public void testFromXContent() throws IOException { SearchHit searchHit = createTestItem(true); boolean humanReadable = randomBoolean(); XContentType xContentType = randomFrom(XContentType.values()); BytesReference originalBytes = toShuffledXContent(searchHit, xContentType, ToXContent.EMPTY_PARAMS, humanReadable); SearchHit parsed; try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) { parser.nextToken(); // jump to first START_OBJECT parsed = SearchHit.fromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); assertNull(parser.nextToken()); } assertToXContentEquivalent(originalBytes, toXContent(parsed, xContentType, humanReadable), xContentType); } /** * When e.g. with "stored_fields": "_none_", only "_index" and "_score" are returned. */ public void testFromXContentWithoutTypeAndId() throws IOException { String hit = "{\"_index\": \"my_index\", \"_score\": 1}"; SearchHit parsed; try (XContentParser parser = createParser(JsonXContent.jsonXContent, hit)) { parser.nextToken(); // jump to first START_OBJECT parsed = SearchHit.fromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); assertNull(parser.nextToken()); } assertEquals("my_index", parsed.getIndex()); assertEquals(1, parsed.getScore(), Float.MIN_VALUE); assertNull(parsed.getType()); assertNull(parsed.getId()); } public void testToXContent() throws IOException { SearchHit searchHit = new SearchHit(1, "id1", new Text("type"), Collections.emptyMap()); searchHit.score(1.5f); XContentBuilder builder = JsonXContent.contentBuilder(); searchHit.toXContent(builder, ToXContent.EMPTY_PARAMS); assertEquals("{\"_type\":\"type\",\"_id\":\"id1\",\"_score\":1.5}", builder.string()); } public void testSerializeShardTarget() throws Exception { SearchShardTarget target = new SearchShardTarget("_node_id", new Index("_index", "_na_"), 0); Map<String, SearchHits> innerHits = new HashMap<>(); SearchHit innerHit1 = new SearchHit(0, "_id", new Text("_type"), null); innerHit1.shard(target); SearchHit innerInnerHit2 = new SearchHit(0, "_id", new Text("_type"), null); innerInnerHit2.shard(target); innerHits.put("1", new SearchHits(new SearchHit[]{innerInnerHit2}, 1, 1f)); innerHit1.setInnerHits(innerHits); SearchHit innerHit2 = new SearchHit(0, "_id", new Text("_type"), null); innerHit2.shard(target); SearchHit innerHit3 = new SearchHit(0, "_id", new Text("_type"), null); innerHit3.shard(target); innerHits = new HashMap<>(); SearchHit hit1 = new SearchHit(0, "_id", new Text("_type"), null); innerHits.put("1", new SearchHits(new SearchHit[]{innerHit1, innerHit2}, 1, 1f)); innerHits.put("2", new SearchHits(new SearchHit[]{innerHit3}, 1, 1f)); hit1.shard(target); hit1.setInnerHits(innerHits); SearchHit hit2 = new SearchHit(0, "_id", new Text("_type"), null); hit2.shard(target); SearchHits hits = new SearchHits(new SearchHit[]{hit1, hit2}, 2, 1f); BytesStreamOutput output = new BytesStreamOutput(); hits.writeTo(output); InputStream input = output.bytes().streamInput(); SearchHits results = SearchHits.readSearchHits(new InputStreamStreamInput(input)); assertThat(results.getAt(0).getShard(), equalTo(target)); assertThat(results.getAt(0).getInnerHits().get("1").getAt(0).getShard(), notNullValue()); assertThat(results.getAt(0).getInnerHits().get("1").getAt(0).getInnerHits().get("1").getAt(0).getShard(), notNullValue()); assertThat(results.getAt(0).getInnerHits().get("1").getAt(1).getShard(), notNullValue()); assertThat(results.getAt(0).getInnerHits().get("2").getAt(0).getShard(), notNullValue()); assertThat(results.getAt(1).getShard(), equalTo(target)); } public void testNullSource() throws Exception { SearchHit searchHit = new SearchHit(0, "_id", new Text("_type"), null); assertThat(searchHit.getSourceAsMap(), nullValue()); assertThat(searchHit.getSourceRef(), nullValue()); assertThat(searchHit.getSourceAsMap(), nullValue()); assertThat(searchHit.getSourceAsString(), nullValue()); assertThat(searchHit.getSourceAsMap(), nullValue()); assertThat(searchHit.getSourceRef(), nullValue()); assertThat(searchHit.getSourceAsString(), nullValue()); } public void testHasSource() { SearchHit searchHit = new SearchHit(randomInt()); assertFalse(searchHit.hasSource()); searchHit.sourceRef(new BytesArray("{}")); assertTrue(searchHit.hasSource()); } private static Explanation createExplanation(int depth) { String description = randomAlphaOfLengthBetween(5, 20); float value = randomFloat(); List<Explanation> details = new ArrayList<>(); if (depth > 0) { int numberOfDetails = randomIntBetween(1, 3); for (int i = 0; i < numberOfDetails; i++) { details.add(createExplanation(depth - 1)); } } return Explanation.match(value, description, details); } }