Merge pull request #15869 from nik9000/cross_fields_numeric
Fix blended terms for non-strings
This commit is contained in:
commit
c2259a2edd
|
@ -390,7 +390,7 @@ public abstract class MappedFieldType extends FieldType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a term associated with the field of this mapper for the given value */
|
/** Creates a term associated with the field of this mapper for the given value */
|
||||||
protected Term createTerm(Object value) {
|
public Term createTerm(Object value) {
|
||||||
return new Term(name(), indexedValueForSearch(value));
|
return new Term(name(), indexedValueForSearch(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -212,10 +212,6 @@ public class MatchQuery {
|
||||||
this.zeroTermsQuery = zeroTermsQuery;
|
this.zeroTermsQuery = zeroTermsQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean forceAnalyzeQueryString() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Analyzer getAnalyzer(MappedFieldType fieldType) {
|
protected Analyzer getAnalyzer(MappedFieldType fieldType) {
|
||||||
if (this.analyzer == null) {
|
if (this.analyzer == null) {
|
||||||
if (fieldType != null) {
|
if (fieldType != null) {
|
||||||
|
@ -240,9 +236,18 @@ public class MatchQuery {
|
||||||
field = fieldName;
|
field = fieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType != null && fieldType.useTermQueryWithQueryString() && !forceAnalyzeQueryString()) {
|
/*
|
||||||
|
* If the user forced an analyzer we really don't care if they are
|
||||||
|
* searching a type that wants term queries to be used with query string
|
||||||
|
* because the QueryBuilder will take care of it. If they haven't forced
|
||||||
|
* an analyzer then types like NumberFieldType that want terms with
|
||||||
|
* query string will blow up because their analyzer isn't capable of
|
||||||
|
* passing through QueryBuilder.
|
||||||
|
*/
|
||||||
|
boolean noForcedAnalyzer = this.analyzer == null;
|
||||||
|
if (fieldType != null && fieldType.useTermQueryWithQueryString() && noForcedAnalyzer) {
|
||||||
try {
|
try {
|
||||||
return fieldType.termQuery(value, context);
|
return termQuery(fieldType, value);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
if (lenient) {
|
if (lenient) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -251,6 +256,7 @@ public class MatchQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Analyzer analyzer = getAnalyzer(fieldType);
|
Analyzer analyzer = getAnalyzer(fieldType);
|
||||||
assert analyzer != null;
|
assert analyzer != null;
|
||||||
MatchQueryBuilder builder = new MatchQueryBuilder(analyzer, fieldType);
|
MatchQueryBuilder builder = new MatchQueryBuilder(analyzer, fieldType);
|
||||||
|
@ -282,6 +288,15 @@ public class MatchQuery {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a TermQuery-like-query for MappedFieldTypes that don't support
|
||||||
|
* QueryBuilder which is very string-ish. Just delegates to the
|
||||||
|
* MappedFieldType for MatchQuery but gets more complex for blended queries.
|
||||||
|
*/
|
||||||
|
protected Query termQuery(MappedFieldType fieldType, Object value) {
|
||||||
|
return fieldType.termQuery(value, context);
|
||||||
|
}
|
||||||
|
|
||||||
protected Query zeroTermsQuery() {
|
protected Query zeroTermsQuery() {
|
||||||
return zeroTermsQuery == DEFAULT_ZERO_TERMS_QUERY ? Queries.newMatchNoDocsQuery() : Queries.newMatchAllQuery();
|
return zeroTermsQuery == DEFAULT_ZERO_TERMS_QUERY ? Queries.newMatchNoDocsQuery() : Queries.newMatchAllQuery();
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,10 @@ public class MultiMatchQuery extends MatchQuery {
|
||||||
public boolean forceAnalyzeQueryString() {
|
public boolean forceAnalyzeQueryString() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Query termQuery(MappedFieldType fieldType, Object value) {
|
||||||
|
return fieldType.termQuery(value, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CrossFieldsQueryBuilder extends QueryBuilder {
|
public class CrossFieldsQueryBuilder extends QueryBuilder {
|
||||||
|
@ -196,8 +200,13 @@ public class MultiMatchQuery extends MatchQuery {
|
||||||
} else {
|
} else {
|
||||||
blendedFields = null;
|
blendedFields = null;
|
||||||
}
|
}
|
||||||
final FieldAndFieldType fieldAndFieldType = group.get(0);
|
/*
|
||||||
Query q = parseGroup(type.matchQueryType(), fieldAndFieldType.field, 1f, value, minimumShouldMatch);
|
* We have to pick some field to pass through the superclass so
|
||||||
|
* we just pick the first field. It shouldn't matter because
|
||||||
|
* fields are already grouped by their analyzers/types.
|
||||||
|
*/
|
||||||
|
String representativeField = group.get(0).field;
|
||||||
|
Query q = parseGroup(type.matchQueryType(), representativeField, 1f, value, minimumShouldMatch);
|
||||||
if (q != null) {
|
if (q != null) {
|
||||||
queries.add(q);
|
queries.add(q);
|
||||||
}
|
}
|
||||||
|
@ -206,6 +215,28 @@ public class MultiMatchQuery extends MatchQuery {
|
||||||
return queries.isEmpty() ? null : queries;
|
return queries.isEmpty() ? null : queries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick the field for parsing. If any of the fields in the group do
|
||||||
|
* *not* useTermQueryWithQueryString then we return that one to force
|
||||||
|
* analysis. If some of the fields would useTermQueryWithQueryString
|
||||||
|
* then we assume that that parsing field's parser is good enough for
|
||||||
|
* them and return it. Otherwise we just return the first field. You
|
||||||
|
* should only get mixed groups like this when you force a certain
|
||||||
|
* analyzer on a query and use string and integer fields because of the
|
||||||
|
* way that grouping is done. That means that the use *asked* for the
|
||||||
|
* integer fields to be searched using a string analyzer so this is
|
||||||
|
* technically doing exactly what they asked for even if it is a bit
|
||||||
|
* funky.
|
||||||
|
*/
|
||||||
|
private String fieldForParsing(List<FieldAndFieldType> group) {
|
||||||
|
for (FieldAndFieldType field: group) {
|
||||||
|
if (field.fieldType.useTermQueryWithQueryString()) {
|
||||||
|
return field.field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return group.get(0).field;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean forceAnalyzeQueryString() {
|
public boolean forceAnalyzeQueryString() {
|
||||||
return blendedFields != null;
|
return blendedFields != null;
|
||||||
|
@ -231,6 +262,11 @@ public class MultiMatchQuery extends MatchQuery {
|
||||||
}
|
}
|
||||||
return BlendedTermQuery.dismaxBlendedQuery(terms, blendedBoost, tieBreaker);
|
return BlendedTermQuery.dismaxBlendedQuery(terms, blendedBoost, tieBreaker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query termQuery(MappedFieldType fieldType, Object value) {
|
||||||
|
return blendTerm(fieldType.createTerm(value), fieldType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -266,7 +302,11 @@ public class MultiMatchQuery extends MatchQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean forceAnalyzeQueryString() {
|
protected Query termQuery(MappedFieldType fieldType, Object value) {
|
||||||
return this.queryBuilder == null ? super.forceAnalyzeQueryString() : this.queryBuilder.forceAnalyzeQueryString();
|
if (queryBuilder == null) {
|
||||||
|
// Can be null when the MultiMatchQuery collapses into a MatchQuery
|
||||||
|
return super.termQuery(fieldType, value);
|
||||||
|
}
|
||||||
|
return queryBuilder.termQuery(fieldType, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.elasticsearch.search.query;
|
package org.elasticsearch.search.query;
|
||||||
|
|
||||||
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
|
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
|
||||||
|
|
||||||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
|
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
|
||||||
import org.elasticsearch.action.index.IndexRequestBuilder;
|
import org.elasticsearch.action.index.IndexRequestBuilder;
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
|
@ -459,6 +460,23 @@ public class MultiMatchQueryIT extends ESIntegTestCase {
|
||||||
assertHitCount(searchResponse, 1l);
|
assertHitCount(searchResponse, 1l);
|
||||||
assertFirstHit(searchResponse, hasId("theone"));
|
assertFirstHit(searchResponse, hasId("theone"));
|
||||||
|
|
||||||
|
searchResponse = client().prepareSearch("test")
|
||||||
|
.setQuery(randomizeType(multiMatchQuery("captain america 15", "full_name", "first_name", "last_name", "category", "skill", "int-field")
|
||||||
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)
|
||||||
|
.analyzer("category")
|
||||||
|
.operator(Operator.AND))).get();
|
||||||
|
assertHitCount(searchResponse, 1l);
|
||||||
|
assertFirstHit(searchResponse, hasId("theone"));
|
||||||
|
|
||||||
|
searchResponse = client().prepareSearch("test")
|
||||||
|
.setQuery(randomizeType(multiMatchQuery("captain america 15", "skill", "full_name", "first_name", "last_name", "category", "int-field")
|
||||||
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)
|
||||||
|
.analyzer("category")
|
||||||
|
.operator(Operator.AND))).get();
|
||||||
|
assertHitCount(searchResponse, 1l);
|
||||||
|
assertFirstHit(searchResponse, hasId("theone"));
|
||||||
|
|
||||||
|
|
||||||
searchResponse = client().prepareSearch("test")
|
searchResponse = client().prepareSearch("test")
|
||||||
.setQuery(randomizeType(multiMatchQuery("captain america 15", "first_name", "last_name", "skill")
|
.setQuery(randomizeType(multiMatchQuery("captain america 15", "first_name", "last_name", "skill")
|
||||||
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)
|
||||||
|
@ -471,6 +489,24 @@ public class MultiMatchQueryIT extends ESIntegTestCase {
|
||||||
.analyzer("category"))).get();
|
.analyzer("category"))).get();
|
||||||
assertFirstHit(searchResponse, hasId("theone"));
|
assertFirstHit(searchResponse, hasId("theone"));
|
||||||
|
|
||||||
|
searchResponse = client().prepareSearch("test")
|
||||||
|
.setQuery(randomizeType(multiMatchQuery("25 15", "first_name", "int-field", "skill")
|
||||||
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)
|
||||||
|
.analyzer("category"))).get();
|
||||||
|
assertFirstHit(searchResponse, hasId("theone"));
|
||||||
|
|
||||||
|
searchResponse = client().prepareSearch("test")
|
||||||
|
.setQuery(randomizeType(multiMatchQuery("25 15", "int-field", "skill", "first_name")
|
||||||
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)
|
||||||
|
.analyzer("category"))).get();
|
||||||
|
assertFirstHit(searchResponse, hasId("theone"));
|
||||||
|
|
||||||
|
searchResponse = client().prepareSearch("test")
|
||||||
|
.setQuery(randomizeType(multiMatchQuery("25 15", "int-field", "first_name", "skill")
|
||||||
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)
|
||||||
|
.analyzer("category"))).get();
|
||||||
|
assertFirstHit(searchResponse, hasId("theone"));
|
||||||
|
|
||||||
searchResponse = client().prepareSearch("test")
|
searchResponse = client().prepareSearch("test")
|
||||||
.setQuery(randomizeType(multiMatchQuery("captain america marvel hero", "first_name", "last_name", "category")
|
.setQuery(randomizeType(multiMatchQuery("captain america marvel hero", "first_name", "last_name", "category")
|
||||||
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)
|
||||||
|
@ -529,6 +565,46 @@ public class MultiMatchQueryIT extends ESIntegTestCase {
|
||||||
assertFirstHit(searchResponse, hasId("ultimate2"));
|
assertFirstHit(searchResponse, hasId("ultimate2"));
|
||||||
assertSecondHit(searchResponse, hasId("ultimate1"));
|
assertSecondHit(searchResponse, hasId("ultimate1"));
|
||||||
assertThat(searchResponse.getHits().hits()[0].getScore(), greaterThan(searchResponse.getHits().hits()[1].getScore()));
|
assertThat(searchResponse.getHits().hits()[0].getScore(), greaterThan(searchResponse.getHits().hits()[1].getScore()));
|
||||||
|
|
||||||
|
// Test group based on numeric fields
|
||||||
|
searchResponse = client().prepareSearch("test")
|
||||||
|
.setQuery(randomizeType(multiMatchQuery("15", "skill")
|
||||||
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS))).get();
|
||||||
|
assertFirstHit(searchResponse, hasId("theone"));
|
||||||
|
|
||||||
|
searchResponse = client().prepareSearch("test")
|
||||||
|
.setQuery(randomizeType(multiMatchQuery("15", "skill", "first_name")
|
||||||
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS))).get();
|
||||||
|
assertFirstHit(searchResponse, hasId("theone"));
|
||||||
|
|
||||||
|
// Two numeric fields together caused trouble at one point!
|
||||||
|
searchResponse = client().prepareSearch("test")
|
||||||
|
.setQuery(randomizeType(multiMatchQuery("15", "int-field", "skill")
|
||||||
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS))).get();
|
||||||
|
assertFirstHit(searchResponse, hasId("theone"));
|
||||||
|
|
||||||
|
searchResponse = client().prepareSearch("test")
|
||||||
|
.setQuery(randomizeType(multiMatchQuery("15", "int-field", "first_name", "skill")
|
||||||
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS))).get();
|
||||||
|
assertFirstHit(searchResponse, hasId("theone"));
|
||||||
|
|
||||||
|
searchResponse = client().prepareSearch("test")
|
||||||
|
.setQuery(randomizeType(multiMatchQuery("alpha 15", "first_name", "skill")
|
||||||
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)
|
||||||
|
.lenient(true))).get();
|
||||||
|
assertFirstHit(searchResponse, hasId("ultimate1"));
|
||||||
|
/*
|
||||||
|
* Doesn't find theone because "alpha 15" isn't a number and we don't
|
||||||
|
* break on spaces.
|
||||||
|
*/
|
||||||
|
assertHitCount(searchResponse, 1);
|
||||||
|
|
||||||
|
// Lenient wasn't always properly lenient with two numeric fields
|
||||||
|
searchResponse = client().prepareSearch("test")
|
||||||
|
.setQuery(randomizeType(multiMatchQuery("alpha 15", "int-field", "first_name", "skill")
|
||||||
|
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)
|
||||||
|
.lenient(true))).get();
|
||||||
|
assertFirstHit(searchResponse, hasId("ultimate1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final void assertEquivalent(String query, SearchResponse left, SearchResponse right) {
|
private static final void assertEquivalent(String query, SearchResponse left, SearchResponse right) {
|
||||||
|
|
Loading…
Reference in New Issue