/* * 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.messy.tests; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.script.Script; import org.elasticsearch.script.groovy.GroovyPlugin; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.test.ESIntegTestCase; import org.junit.Test; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.ExecutionException; import static org.elasticsearch.client.Requests.searchRequest; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.index.query.QueryBuilders.functionScoreQuery; import static org.elasticsearch.index.query.QueryBuilders.termQuery; import static org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders.scriptFunction; import static org.elasticsearch.search.aggregations.AggregationBuilders.terms; import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; public class FunctionScoreTests extends ESIntegTestCase { static final String TYPE = "type"; static final String INDEX = "index"; @Override protected Collection<Class<? extends Plugin>> nodePlugins() { return pluginList(GroovyPlugin.class); } @Test public void testScriptScoresNested() throws IOException { createIndex(INDEX); ensureYellow(); index(INDEX, TYPE, "1", jsonBuilder().startObject().field("dummy_field", 1).endObject()); refresh(); SearchResponse response = client().search( searchRequest().source( searchSource().query( functionScoreQuery( functionScoreQuery( functionScoreQuery().add(scriptFunction(new Script("1")))).add( scriptFunction(new Script("_score.doubleValue()")))).add( scriptFunction(new Script("_score.doubleValue()")) ) ) ) ).actionGet(); assertSearchResponse(response); assertThat(response.getHits().getAt(0).score(), equalTo(1.0f)); } @Test public void testScriptScoresWithAgg() throws IOException { createIndex(INDEX); ensureYellow(); index(INDEX, TYPE, "1", jsonBuilder().startObject().field("dummy_field", 1).endObject()); refresh(); SearchResponse response = client().search( searchRequest().source( searchSource().query(functionScoreQuery().add(scriptFunction(new Script("_score.doubleValue()")))).aggregation( terms("score_agg").script(new Script("_score.doubleValue()"))) ) ).actionGet(); assertSearchResponse(response); assertThat(response.getHits().getAt(0).score(), equalTo(1.0f)); assertThat(((Terms) response.getAggregations().asMap().get("score_agg")).getBuckets().get(0).getKeyAsString(), equalTo("1.0")); assertThat(((Terms) response.getAggregations().asMap().get("score_agg")).getBuckets().get(0).getDocCount(), is(1l)); } public void testMinScoreFunctionScoreBasic() throws IOException { index(INDEX, TYPE, jsonBuilder().startObject().field("num", 2).endObject()); refresh(); ensureYellow(); float score = randomFloat(); float minScore = randomFloat(); SearchResponse searchResponse = client().search( searchRequest().source( searchSource().query( functionScoreQuery().add(scriptFunction(new Script(Float.toString(score)))).setMinScore(minScore))) ).actionGet(); if (score < minScore) { assertThat(searchResponse.getHits().getTotalHits(), is(0l)); } else { assertThat(searchResponse.getHits().getTotalHits(), is(1l)); } searchResponse = client().search( searchRequest().source(searchSource().query(functionScoreQuery() .add(scriptFunction(new Script(Float.toString(score)))) .add(scriptFunction(new Script(Float.toString(score)))) .scoreMode("avg").setMinScore(minScore))) ).actionGet(); if (score < minScore) { assertThat(searchResponse.getHits().getTotalHits(), is(0l)); } else { assertThat(searchResponse.getHits().getTotalHits(), is(1l)); } } @Test public void testMinScoreFunctionScoreManyDocsAndRandomMinScore() throws IOException, ExecutionException, InterruptedException { List<IndexRequestBuilder> docs = new ArrayList<>(); int numDocs = randomIntBetween(1, 100); int scoreOffset = randomIntBetween(-2 * numDocs, 2 * numDocs); int minScore = randomIntBetween(-2 * numDocs, 2 * numDocs); for (int i = 0; i < numDocs; i++) { docs.add(client().prepareIndex(INDEX, TYPE, Integer.toString(i)).setSource("num", i + scoreOffset)); } indexRandom(true, docs); ensureYellow(); Script script = new Script("return (doc['num'].value)"); int numMatchingDocs = numDocs + scoreOffset - minScore; if (numMatchingDocs < 0) { numMatchingDocs = 0; } if (numMatchingDocs > numDocs) { numMatchingDocs = numDocs; } SearchResponse searchResponse = client().search( searchRequest().source(searchSource().query(functionScoreQuery() .add(scriptFunction(script)) .setMinScore(minScore)).size(numDocs))).actionGet(); assertMinScoreSearchResponses(numDocs, searchResponse, numMatchingDocs); searchResponse = client().search( searchRequest().source(searchSource().query(functionScoreQuery() .add(scriptFunction(script)) .add(scriptFunction(script)) .scoreMode("avg").setMinScore(minScore)).size(numDocs))).actionGet(); assertMinScoreSearchResponses(numDocs, searchResponse, numMatchingDocs); } protected void assertMinScoreSearchResponses(int numDocs, SearchResponse searchResponse, int numMatchingDocs) { assertSearchResponse(searchResponse); assertThat((int) searchResponse.getHits().totalHits(), is(numMatchingDocs)); int pos = 0; for (int hitId = numDocs - 1; (numDocs - hitId) < searchResponse.getHits().totalHits(); hitId--) { assertThat(searchResponse.getHits().getAt(pos).getId(), equalTo(Integer.toString(hitId))); pos++; } } @Test public void testWithEmptyFunctions() throws IOException, ExecutionException, InterruptedException { assertAcked(prepareCreate("test")); ensureYellow(); index("test", "testtype", "1", jsonBuilder().startObject().field("text", "test text").endObject()); refresh(); // make sure that min_score works if functions is empty, see https://github.com/elastic/elasticsearch/issues/10253 float termQueryScore = 0.19178301f; testMinScoreApplied("sum", termQueryScore); testMinScoreApplied("avg", termQueryScore); testMinScoreApplied("max", termQueryScore); testMinScoreApplied("min", termQueryScore); testMinScoreApplied("multiply", termQueryScore); testMinScoreApplied("replace", termQueryScore); } protected void testMinScoreApplied(String boostMode, float expectedScore) throws InterruptedException, ExecutionException { SearchResponse response = client().search( searchRequest().source( searchSource().explain(true).query( functionScoreQuery(termQuery("text", "text")).boostMode(boostMode).setMinScore(0.1f)))).get(); assertSearchResponse(response); assertThat(response.getHits().totalHits(), equalTo(1l)); assertThat(response.getHits().getAt(0).getScore(), equalTo(expectedScore)); response = client().search( searchRequest().source( searchSource().explain(true).query( functionScoreQuery(termQuery("text", "text")).boostMode(boostMode).setMinScore(2f)))).get(); assertSearchResponse(response); assertThat(response.getHits().totalHits(), equalTo(0l)); } }