From 9a3a1cd1b73149281d5ed19bf0c89d8ef641a752 Mon Sep 17 00:00:00 2001 From: Alexander Kazakov Date: Thu, 19 Oct 2017 11:29:28 +0300 Subject: [PATCH] Handle leniency for cross_fields type in multi_match query (#27045) --- .../index/search/MultiMatchQuery.java | 29 ++++++++----------- .../index/search/MultiMatchQueryTests.java | 29 +++++++++++++++---- .../search/query/MultiMatchQueryIT.java | 4 +++ 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/search/MultiMatchQuery.java b/core/src/main/java/org/elasticsearch/index/search/MultiMatchQuery.java index c67cceec0d1..61029f70e8f 100644 --- a/core/src/main/java/org/elasticsearch/index/search/MultiMatchQuery.java +++ b/core/src/main/java/org/elasticsearch/index/search/MultiMatchQuery.java @@ -43,6 +43,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import static org.elasticsearch.common.lucene.search.Queries.newLenientFieldQuery; + public class MultiMatchQuery extends MatchQuery { private Float groupTieBreaker = null; @@ -204,7 +206,7 @@ public class MultiMatchQuery extends MatchQuery { for (int i = 0; i < terms.length; i++) { values[i] = terms[i].bytes(); } - return MultiMatchQuery.blendTerms(context, values, commonTermsCutoff, tieBreaker, blendedFields); + return MultiMatchQuery.blendTerms(context, values, commonTermsCutoff, tieBreaker, lenient, blendedFields); } @Override @@ -212,7 +214,7 @@ public class MultiMatchQuery extends MatchQuery { if (blendedFields == null) { return super.blendTerm(term, fieldType); } - return MultiMatchQuery.blendTerm(context, term.bytes(), commonTermsCutoff, tieBreaker, blendedFields); + return MultiMatchQuery.blendTerm(context, term.bytes(), commonTermsCutoff, tieBreaker, lenient, blendedFields); } @Override @@ -227,12 +229,12 @@ public class MultiMatchQuery extends MatchQuery { } static Query blendTerm(QueryShardContext context, BytesRef value, Float commonTermsCutoff, float tieBreaker, - FieldAndFieldType... blendedFields) { - return blendTerms(context, new BytesRef[] {value}, commonTermsCutoff, tieBreaker, blendedFields); + boolean lenient, FieldAndFieldType... blendedFields) { + return blendTerms(context, new BytesRef[] {value}, commonTermsCutoff, tieBreaker, lenient, blendedFields); } static Query blendTerms(QueryShardContext context, BytesRef[] values, Float commonTermsCutoff, float tieBreaker, - FieldAndFieldType... blendedFields) { + boolean lenient, FieldAndFieldType... blendedFields) { List queries = new ArrayList<>(); Term[] terms = new Term[blendedFields.length * values.length]; float[] blendedBoost = new float[blendedFields.length * values.length]; @@ -242,19 +244,12 @@ public class MultiMatchQuery extends MatchQuery { Query query; try { query = ft.fieldType.termQuery(term, context); - } catch (IllegalArgumentException e) { - // the query expects a certain class of values such as numbers - // of ip addresses and the value can't be parsed, so ignore this - // field - continue; - } catch (ElasticsearchParseException parseException) { - // date fields throw an ElasticsearchParseException with the - // underlying IAE as the cause, ignore this field if that is - // the case - if (parseException.getCause() instanceof IllegalArgumentException) { - continue; + } catch (RuntimeException e) { + if (lenient) { + query = newLenientFieldQuery(ft.fieldType.name(), e); + } else { + throw e; } - throw parseException; } float boost = ft.boost; while (query instanceof BoostQuery) { diff --git a/core/src/test/java/org/elasticsearch/index/search/MultiMatchQueryTests.java b/core/src/test/java/org/elasticsearch/index/search/MultiMatchQueryTests.java index 70010cdfc22..5695094553d 100644 --- a/core/src/test/java/org/elasticsearch/index/search/MultiMatchQueryTests.java +++ b/core/src/test/java/org/elasticsearch/index/search/MultiMatchQueryTests.java @@ -30,6 +30,7 @@ import org.apache.lucene.search.SynonymQuery; import org.apache.lucene.search.TermQuery; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.engine.Engine; @@ -110,7 +111,7 @@ public class MultiMatchQueryTests extends ESSingleNodeTestCase { Query expected = BlendedTermQuery.dismaxBlendedQuery(terms, boosts, 1.0f); Query actual = MultiMatchQuery.blendTerm( indexService.newQueryShardContext(randomInt(20), null, () -> { throw new UnsupportedOperationException(); }, null), - new BytesRef("baz"), null, 1f, new FieldAndFieldType(ft1, 2), new FieldAndFieldType(ft2, 3)); + new BytesRef("baz"), null, 1f, false, new FieldAndFieldType(ft1, 2), new FieldAndFieldType(ft2, 3)); assertEquals(expected, actual); } @@ -126,11 +127,11 @@ public class MultiMatchQueryTests extends ESSingleNodeTestCase { Query expected = BlendedTermQuery.dismaxBlendedQuery(terms, boosts, 1.0f); Query actual = MultiMatchQuery.blendTerm( indexService.newQueryShardContext(randomInt(20), null, () -> { throw new UnsupportedOperationException(); }, null), - new BytesRef("baz"), null, 1f, new FieldAndFieldType(ft1, 2), new FieldAndFieldType(ft2, 3)); + new BytesRef("baz"), null, 1f, false, new FieldAndFieldType(ft1, 2), new FieldAndFieldType(ft2, 3)); assertEquals(expected, actual); } - public void testBlendTermsUnsupportedValue() { + public void testBlendTermsUnsupportedValueWithLenient() { FakeFieldType ft1 = new FakeFieldType(); ft1.setName("foo"); FakeFieldType ft2 = new FakeFieldType() { @@ -142,13 +143,29 @@ public class MultiMatchQueryTests extends ESSingleNodeTestCase { ft2.setName("bar"); Term[] terms = new Term[] { new Term("foo", "baz") }; float[] boosts = new float[] {2}; - Query expected = BlendedTermQuery.dismaxBlendedQuery(terms, boosts, 1.0f); + Query expected = new DisjunctionMaxQuery(Arrays.asList( + Queries.newMatchNoDocsQuery("failed [" + ft2.name() + "] query, caused by illegal_argument_exception:[null]"), + BlendedTermQuery.dismaxBlendedQuery(terms, boosts, 1.0f) + ), 1f); Query actual = MultiMatchQuery.blendTerm( indexService.newQueryShardContext(randomInt(20), null, () -> { throw new UnsupportedOperationException(); }, null), - new BytesRef("baz"), null, 1f, new FieldAndFieldType(ft1, 2), new FieldAndFieldType(ft2, 3)); + new BytesRef("baz"), null, 1f, true, new FieldAndFieldType(ft1, 2), new FieldAndFieldType(ft2, 3)); assertEquals(expected, actual); } + public void testBlendTermsUnsupportedValueWithoutLenient() { + FakeFieldType ft = new FakeFieldType() { + @Override + public Query termQuery(Object value, QueryShardContext context) { + throw new IllegalArgumentException(); + } + }; + ft.setName("bar"); + expectThrows(IllegalArgumentException.class, () -> MultiMatchQuery.blendTerm( + indexService.newQueryShardContext(randomInt(20), null, () -> { throw new UnsupportedOperationException(); }, null), + new BytesRef("baz"), null, 1f, false, new FieldAndFieldType(ft, 1))); + } + public void testBlendNoTermQuery() { FakeFieldType ft1 = new FakeFieldType(); ft1.setName("foo"); @@ -170,7 +187,7 @@ public class MultiMatchQueryTests extends ESSingleNodeTestCase { ), 1.0f); Query actual = MultiMatchQuery.blendTerm( indexService.newQueryShardContext(randomInt(20), null, () -> { throw new UnsupportedOperationException(); }, null), - new BytesRef("baz"), null, 1f, new FieldAndFieldType(ft1, 2), new FieldAndFieldType(ft2, 3)); + new BytesRef("baz"), null, 1f, false, new FieldAndFieldType(ft1, 2), new FieldAndFieldType(ft2, 3)); assertEquals(expected, actual); } diff --git a/core/src/test/java/org/elasticsearch/search/query/MultiMatchQueryIT.java b/core/src/test/java/org/elasticsearch/search/query/MultiMatchQueryIT.java index 37ffda5f46a..fd619b69c9e 100644 --- a/core/src/test/java/org/elasticsearch/search/query/MultiMatchQueryIT.java +++ b/core/src/test/java/org/elasticsearch/search/query/MultiMatchQueryIT.java @@ -472,6 +472,7 @@ public class MultiMatchQueryIT extends ESIntegTestCase { .setQuery(randomizeType(multiMatchQuery("captain america 15", "full_name", "first_name", "last_name", "category", "skill") .type(MultiMatchQueryBuilder.Type.CROSS_FIELDS) .analyzer("category") + .lenient(true) .operator(Operator.AND))).get(); assertHitCount(searchResponse, 1L); assertFirstHit(searchResponse, hasId("theone")); @@ -480,6 +481,7 @@ public class MultiMatchQueryIT extends ESIntegTestCase { .setQuery(randomizeType(multiMatchQuery("captain america 15", "full_name", "first_name", "last_name", "category", "skill", "int-field") .type(MultiMatchQueryBuilder.Type.CROSS_FIELDS) .analyzer("category") + .lenient(true) .operator(Operator.AND))).get(); assertHitCount(searchResponse, 1L); assertFirstHit(searchResponse, hasId("theone")); @@ -488,6 +490,7 @@ public class MultiMatchQueryIT extends ESIntegTestCase { .setQuery(randomizeType(multiMatchQuery("captain america 15", "skill", "full_name", "first_name", "last_name", "category", "int-field") .type(MultiMatchQueryBuilder.Type.CROSS_FIELDS) .analyzer("category") + .lenient(true) .operator(Operator.AND))).get(); assertHitCount(searchResponse, 1L); assertFirstHit(searchResponse, hasId("theone")); @@ -496,6 +499,7 @@ public class MultiMatchQueryIT extends ESIntegTestCase { searchResponse = client().prepareSearch("test") .setQuery(randomizeType(multiMatchQuery("captain america 15", "first_name", "last_name", "skill") .type(MultiMatchQueryBuilder.Type.CROSS_FIELDS) + .lenient(true) .analyzer("category"))).get(); assertFirstHit(searchResponse, hasId("theone"));