Browse Source

Throw a parsing exception when boost is set in span_or query (#28390) (#34112)

Christophe Bismuth 6 years ago
parent
commit
b95a4db6e6
18 changed files with 474 additions and 49 deletions
  1. 4 2
      docs/reference/migration/migrate_7_0/search.asciidoc
  2. 7 1
      docs/reference/query-dsl/span-queries.asciidoc
  3. 4 0
      server/src/main/java/org/elasticsearch/index/query/SpanContainingQueryBuilder.java
  4. 6 3
      server/src/main/java/org/elasticsearch/index/query/SpanFirstQueryBuilder.java
  5. 6 2
      server/src/main/java/org/elasticsearch/index/query/SpanNearQueryBuilder.java
  6. 9 5
      server/src/main/java/org/elasticsearch/index/query/SpanNotQueryBuilder.java
  7. 7 3
      server/src/main/java/org/elasticsearch/index/query/SpanOrQueryBuilder.java
  8. 29 1
      server/src/main/java/org/elasticsearch/index/query/SpanQueryBuilder.java
  9. 4 0
      server/src/main/java/org/elasticsearch/index/query/SpanWithinQueryBuilder.java
  10. 90 1
      server/src/test/java/org/elasticsearch/index/query/SpanContainingQueryBuilderTests.java
  11. 26 2
      server/src/test/java/org/elasticsearch/index/query/SpanFirstQueryBuilderTests.java
  12. 38 1
      server/src/test/java/org/elasticsearch/index/query/SpanNearQueryBuilderTests.java
  13. 96 4
      server/src/test/java/org/elasticsearch/index/query/SpanNotQueryBuilderTests.java
  14. 24 1
      server/src/test/java/org/elasticsearch/index/query/SpanOrQueryBuilderTests.java
  15. 3 6
      server/src/test/java/org/elasticsearch/index/query/SpanTermQueryBuilderTests.java
  16. 90 1
      server/src/test/java/org/elasticsearch/index/query/SpanWithinQueryBuilderTests.java
  17. 6 1
      server/src/test/java/org/elasticsearch/index/query/WrapperQueryBuilderTests.java
  18. 25 15
      test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java

+ 4 - 2
docs/reference/migration/migrate_7_0/search.asciidoc

@@ -18,12 +18,14 @@
     now include entire geohash cell, instead of just geohash center.
 
 *   Attempts to generate multi-term phrase queries against non-text fields
-    with a custom analyzer will now throw an exception
+    with a custom analyzer will now throw an exception.
 
 *   An `envelope` crossing the dateline in a `geo_shape `query is now processed
     correctly when specified using REST API instead of having its left and
     right corners flipped.
 
+*   Attempts to set `boost` on inner span queries will now throw a parsing exception.
+
 [float]
 ==== Adaptive replica selection enabled by default
 
@@ -156,4 +158,4 @@ To deboost a specific query you can use a `boost` comprise between 0 and 1.
 Negative scores in the Function Score Query are deprecated in 6.x, and are
 not allowed in this version. If a negative score is produced as a result
 of computation (e.g. in `script_score` or `field_value_factor` functions),
-an error will be thrown.
+an error will be thrown.

+ 7 - 1
docs/reference/query-dsl/span-queries.asciidoc

@@ -5,6 +5,12 @@ Span queries are low-level positional queries which provide expert control
 over the order and proximity of the specified terms. These are typically used
 to implement very specific queries on legal documents or patents.
 
+It is only allowed to set boost on an outer span query. Compound span queries,
+like span_near, only use the list of matching spans of inner span queries in
+order to find their own spans, which they then use to produce a score. Scores
+are never computed on inner span queries, which is the reason why boosts are not
+allowed: they only influence the way scores are computed, not spans.
+
 Span queries cannot be mixed with non-span queries (with the exception of the `span_multi` query).
 
 The queries in this group are:
@@ -67,4 +73,4 @@ include::span-containing-query.asciidoc[]
 
 include::span-within-query.asciidoc[]
 
-include::span-field-masking-query.asciidoc[]
+include::span-field-masking-query.asciidoc[]

+ 4 - 0
server/src/main/java/org/elasticsearch/index/query/SpanContainingQueryBuilder.java

@@ -32,6 +32,8 @@ import org.elasticsearch.common.xcontent.XContentParser;
 import java.io.IOException;
 import java.util.Objects;
 
+import static org.elasticsearch.index.query.SpanQueryBuilder.SpanQueryBuilderUtil.checkNoBoost;
+
 /**
  * Builder for {@link org.apache.lucene.search.spans.SpanContainingQuery}.
  */
@@ -117,12 +119,14 @@ public class SpanContainingQueryBuilder extends AbstractQueryBuilder<SpanContain
                         throw new ParsingException(parser.getTokenLocation(), "span_containing [big] must be of type span query");
                     }
                     big = (SpanQueryBuilder) query;
+                    checkNoBoost(NAME, currentFieldName, parser, big);
                 } else if (LITTLE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
                     QueryBuilder query = parseInnerQueryBuilder(parser);
                     if (query instanceof SpanQueryBuilder == false) {
                         throw new ParsingException(parser.getTokenLocation(), "span_containing [little] must be of type span query");
                     }
                     little = (SpanQueryBuilder) query;
+                    checkNoBoost(NAME, currentFieldName, parser, little);
                 } else {
                     throw new ParsingException(parser.getTokenLocation(),
                             "[span_containing] query does not support [" + currentFieldName + "]");

+ 6 - 3
server/src/main/java/org/elasticsearch/index/query/SpanFirstQueryBuilder.java

@@ -32,6 +32,8 @@ import org.elasticsearch.common.xcontent.XContentParser;
 import java.io.IOException;
 import java.util.Objects;
 
+import static org.elasticsearch.index.query.SpanQueryBuilder.SpanQueryBuilderUtil.checkNoBoost;
+
 public class SpanFirstQueryBuilder extends AbstractQueryBuilder<SpanFirstQueryBuilder> implements SpanQueryBuilder {
     public static final String NAME = "span_first";
 
@@ -115,9 +117,10 @@ public class SpanFirstQueryBuilder extends AbstractQueryBuilder<SpanFirstQueryBu
                 if (MATCH_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
                     QueryBuilder query = parseInnerQueryBuilder(parser);
                     if (query instanceof SpanQueryBuilder == false) {
-                        throw new ParsingException(parser.getTokenLocation(), "spanFirst [match] must be of type span query");
+                        throw new ParsingException(parser.getTokenLocation(), "span_first [match] must be of type span query");
                     }
                     match = (SpanQueryBuilder) query;
+                    checkNoBoost(NAME, currentFieldName, parser, match);
                 } else {
                     throw new ParsingException(parser.getTokenLocation(), "[span_first] query does not support [" + currentFieldName + "]");
                 }
@@ -134,10 +137,10 @@ public class SpanFirstQueryBuilder extends AbstractQueryBuilder<SpanFirstQueryBu
             }
         }
         if (match == null) {
-            throw new ParsingException(parser.getTokenLocation(), "spanFirst must have [match] span query clause");
+            throw new ParsingException(parser.getTokenLocation(), "span_first must have [match] span query clause");
         }
         if (end == null) {
-            throw new ParsingException(parser.getTokenLocation(), "spanFirst must have [end] set for it");
+            throw new ParsingException(parser.getTokenLocation(), "span_first must have [end] set for it");
         }
         SpanFirstQueryBuilder queryBuilder = new SpanFirstQueryBuilder(match, end);
         queryBuilder.boost(boost).queryName(queryName);

+ 6 - 2
server/src/main/java/org/elasticsearch/index/query/SpanNearQueryBuilder.java

@@ -38,6 +38,8 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
+import static org.elasticsearch.index.query.SpanQueryBuilder.SpanQueryBuilderUtil.checkNoBoost;
+
 /**
  * Matches spans which are near one another. One can specify slop, the maximum number
  * of intervening unmatched positions, as well as whether matches are required to be in-order.
@@ -166,9 +168,11 @@ public class SpanNearQueryBuilder extends AbstractQueryBuilder<SpanNearQueryBuil
                     while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
                         QueryBuilder query = parseInnerQueryBuilder(parser);
                         if (query instanceof SpanQueryBuilder == false) {
-                            throw new ParsingException(parser.getTokenLocation(), "spanNear [clauses] must be of type span query");
+                            throw new ParsingException(parser.getTokenLocation(), "span_near [clauses] must be of type span query");
                         }
-                        clauses.add((SpanQueryBuilder) query);
+                        final SpanQueryBuilder clause = (SpanQueryBuilder) query;
+                        checkNoBoost(NAME, currentFieldName, parser, clause);
+                        clauses.add(clause);
                     }
                 } else {
                     throw new ParsingException(parser.getTokenLocation(), "[span_near] query does not support [" + currentFieldName + "]");

+ 9 - 5
server/src/main/java/org/elasticsearch/index/query/SpanNotQueryBuilder.java

@@ -32,6 +32,8 @@ import org.elasticsearch.common.xcontent.XContentParser;
 import java.io.IOException;
 import java.util.Objects;
 
+import static org.elasticsearch.index.query.SpanQueryBuilder.SpanQueryBuilderUtil.checkNoBoost;
+
 public class SpanNotQueryBuilder extends AbstractQueryBuilder<SpanNotQueryBuilder> implements SpanQueryBuilder {
     public static final String NAME = "span_not";
 
@@ -181,15 +183,17 @@ public class SpanNotQueryBuilder extends AbstractQueryBuilder<SpanNotQueryBuilde
                 if (INCLUDE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
                     QueryBuilder query = parseInnerQueryBuilder(parser);
                     if (query instanceof SpanQueryBuilder == false) {
-                        throw new ParsingException(parser.getTokenLocation(), "spanNot [include] must be of type span query");
+                        throw new ParsingException(parser.getTokenLocation(), "span_not [include] must be of type span query");
                     }
                     include = (SpanQueryBuilder) query;
+                    checkNoBoost(NAME, currentFieldName, parser, include);
                 } else if (EXCLUDE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
                     QueryBuilder query = parseInnerQueryBuilder(parser);
                     if (query instanceof SpanQueryBuilder == false) {
-                        throw new ParsingException(parser.getTokenLocation(), "spanNot [exclude] must be of type span query");
+                        throw new ParsingException(parser.getTokenLocation(), "span_not [exclude] must be of type span query");
                     }
                     exclude = (SpanQueryBuilder) query;
+                    checkNoBoost(NAME, currentFieldName, parser, exclude);
                 } else {
                     throw new ParsingException(parser.getTokenLocation(), "[span_not] query does not support [" + currentFieldName + "]");
                 }
@@ -210,13 +214,13 @@ public class SpanNotQueryBuilder extends AbstractQueryBuilder<SpanNotQueryBuilde
             }
         }
         if (include == null) {
-            throw new ParsingException(parser.getTokenLocation(), "spanNot must have [include] span query clause");
+            throw new ParsingException(parser.getTokenLocation(), "span_not must have [include] span query clause");
         }
         if (exclude == null) {
-            throw new ParsingException(parser.getTokenLocation(), "spanNot must have [exclude] span query clause");
+            throw new ParsingException(parser.getTokenLocation(), "span_not must have [exclude] span query clause");
         }
         if (dist != null && (pre != null || post != null)) {
-            throw new ParsingException(parser.getTokenLocation(), "spanNot can either use [dist] or [pre] & [post] (or none)");
+            throw new ParsingException(parser.getTokenLocation(), "span_not can either use [dist] or [pre] & [post] (or none)");
         }
 
         SpanNotQueryBuilder spanNotQuery = new SpanNotQueryBuilder(include, exclude);

+ 7 - 3
server/src/main/java/org/elasticsearch/index/query/SpanOrQueryBuilder.java

@@ -35,6 +35,8 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
+import static org.elasticsearch.index.query.SpanQueryBuilder.SpanQueryBuilderUtil.checkNoBoost;
+
 /**
  * Span query that matches the union of its clauses. Maps to {@link SpanOrQuery}.
  */
@@ -113,9 +115,11 @@ public class SpanOrQueryBuilder extends AbstractQueryBuilder<SpanOrQueryBuilder>
                     while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
                         QueryBuilder query = parseInnerQueryBuilder(parser);
                         if (query instanceof SpanQueryBuilder == false) {
-                            throw new ParsingException(parser.getTokenLocation(), "spanOr [clauses] must be of type span query");
+                            throw new ParsingException(parser.getTokenLocation(), "span_or [clauses] must be of type span query");
                         }
-                        clauses.add((SpanQueryBuilder) query);
+                        final SpanQueryBuilder clause = (SpanQueryBuilder) query;
+                        checkNoBoost(NAME, currentFieldName, parser, clause);
+                        clauses.add(clause);
                     }
                 } else {
                     throw new ParsingException(parser.getTokenLocation(), "[span_or] query does not support [" + currentFieldName + "]");
@@ -132,7 +136,7 @@ public class SpanOrQueryBuilder extends AbstractQueryBuilder<SpanOrQueryBuilder>
         }
 
         if (clauses.isEmpty()) {
-            throw new ParsingException(parser.getTokenLocation(), "spanOr must include [clauses]");
+            throw new ParsingException(parser.getTokenLocation(), "span_or must include [clauses]");
         }
 
         SpanOrQueryBuilder queryBuilder = new SpanOrQueryBuilder(clauses.get(0));

+ 29 - 1
server/src/main/java/org/elasticsearch/index/query/SpanQueryBuilder.java

@@ -19,9 +19,37 @@
 
 package org.elasticsearch.index.query;
 
+import org.elasticsearch.common.ParsingException;
+import org.elasticsearch.common.xcontent.XContentParser;
+
 /**
- * Marker interface for a specific type of {@link QueryBuilder} that allows to build span queries
+ * Marker interface for a specific type of {@link QueryBuilder} that allows to build span queries.
  */
 public interface SpanQueryBuilder extends QueryBuilder {
 
+    class SpanQueryBuilderUtil {
+        private SpanQueryBuilderUtil() {
+            // utility class
+        }
+
+        /**
+         * Checks boost value of a nested span clause is equal to {@link AbstractQueryBuilder#DEFAULT_BOOST}.
+         *
+         * @param queryName a query name
+         * @param fieldName a field name
+         * @param parser    a parser
+         * @param clause    a span query builder
+         * @throws ParsingException if query boost value isn't equal to {@link AbstractQueryBuilder#DEFAULT_BOOST}
+         */
+        static void checkNoBoost(String queryName, String fieldName, XContentParser parser, SpanQueryBuilder clause) {
+            try {
+                if (clause.boost() != AbstractQueryBuilder.DEFAULT_BOOST) {
+                    throw new ParsingException(parser.getTokenLocation(), queryName + " [" + fieldName + "] " +
+                        "as a nested span clause can't have non-default boost value [" + clause.boost() + "]");
+                }
+            } catch (UnsupportedOperationException ignored) {
+                // if boost is unsupported it can't have been set
+            }
+        }
+    }
 }

+ 4 - 0
server/src/main/java/org/elasticsearch/index/query/SpanWithinQueryBuilder.java

@@ -32,6 +32,8 @@ import org.elasticsearch.common.xcontent.XContentParser;
 import java.io.IOException;
 import java.util.Objects;
 
+import static org.elasticsearch.index.query.SpanQueryBuilder.SpanQueryBuilderUtil.checkNoBoost;
+
 /**
  * Builder for {@link org.apache.lucene.search.spans.SpanWithinQuery}.
  */
@@ -122,12 +124,14 @@ public class SpanWithinQueryBuilder extends AbstractQueryBuilder<SpanWithinQuery
                         throw new ParsingException(parser.getTokenLocation(), "span_within [big] must be of type span query");
                     }
                     big = (SpanQueryBuilder) query;
+                    checkNoBoost(NAME, currentFieldName, parser, big);
                 } else if (LITTLE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
                     QueryBuilder query = parseInnerQueryBuilder(parser);
                     if (query instanceof SpanQueryBuilder == false) {
                         throw new ParsingException(parser.getTokenLocation(), "span_within [little] must be of type span query");
                     }
                     little = (SpanQueryBuilder) query;
+                    checkNoBoost(NAME, currentFieldName, parser, little);
                 } else {
                     throw new ParsingException(parser.getTokenLocation(),
                             "[span_within] query does not support [" + currentFieldName + "]");

+ 90 - 1
server/src/test/java/org/elasticsearch/index/query/SpanContainingQueryBuilderTests.java

@@ -21,11 +21,13 @@ package org.elasticsearch.index.query;
 
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.spans.SpanContainingQuery;
+import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.search.internal.SearchContext;
 import org.elasticsearch.test.AbstractQueryTestCase;
 
 import java.io.IOException;
 
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.instanceOf;
 
 public class SpanContainingQueryBuilderTests extends AbstractQueryTestCase<SpanContainingQueryBuilder> {
@@ -80,7 +82,7 @@ public class SpanContainingQueryBuilderTests extends AbstractQueryTestCase<SpanC
                 "        }\n" +
                 "      }\n" +
                 "    },\n" +
-                "    \"boost\" : 1.0\n" +
+                "    \"boost\" : 2.0\n" +
                 "  }\n" +
                 "}";
 
@@ -89,5 +91,92 @@ public class SpanContainingQueryBuilderTests extends AbstractQueryTestCase<SpanC
 
         assertEquals(json, 2, ((SpanNearQueryBuilder) parsed.bigQuery()).clauses().size());
         assertEquals(json, "foo", ((SpanTermQueryBuilder) parsed.littleQuery()).value());
+        assertEquals(json, 2.0, parsed.boost(), 0.0);
+    }
+
+    public void testFromJsoWithNonDefaultBoostInBigQuery() {
+        String json =
+                "{\n" +
+                "  \"span_containing\" : {\n" +
+                "    \"big\" : {\n" +
+                "      \"span_near\" : {\n" +
+                "        \"clauses\" : [ {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"bar\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        }, {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"baz\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        } ],\n" +
+                "        \"slop\" : 5,\n" +
+                "        \"in_order\" : true,\n" +
+                "        \"boost\" : 2.0\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"little\" : {\n" +
+                "      \"span_term\" : {\n" +
+                "        \"field1\" : {\n" +
+                "          \"value\" : \"foo\",\n" +
+                "          \"boost\" : 1.0\n" +
+                "        }\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"boost\" : 1.0\n" +
+                "  }\n" +
+                "}";
+
+        Exception exception = expectThrows(ParsingException.class, () -> parseQuery(json));
+        assertThat(exception.getMessage(),
+            equalTo("span_containing [big] as a nested span clause can't have non-default boost value [2.0]"));
+    }
+
+    public void testFromJsonWithNonDefaultBoostInLittleQuery() {
+        String json =
+                "{\n" +
+                "  \"span_containing\" : {\n" +
+                "    \"little\" : {\n" +
+                "      \"span_near\" : {\n" +
+                "        \"clauses\" : [ {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"bar\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        }, {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"baz\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        } ],\n" +
+                "        \"slop\" : 5,\n" +
+                "        \"in_order\" : true,\n" +
+                "        \"boost\" : 2.0\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"big\" : {\n" +
+                "      \"span_term\" : {\n" +
+                "        \"field1\" : {\n" +
+                "          \"value\" : \"foo\",\n" +
+                "          \"boost\" : 1.0\n" +
+                "        }\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"boost\" : 1.0\n" +
+                "  }\n" +
+                "}";
+
+        Exception exception = expectThrows(ParsingException.class, () -> parseQuery(json));
+        assertThat(exception.getMessage(),
+            equalTo("span_containing [little] as a nested span clause can't have non-default boost value [2.0]"));
     }
 }

+ 26 - 2
server/src/test/java/org/elasticsearch/index/query/SpanFirstQueryBuilderTests.java

@@ -31,6 +31,7 @@ import org.elasticsearch.test.AbstractQueryTestCase;
 import java.io.IOException;
 
 import static org.elasticsearch.index.query.QueryBuilders.spanTermQuery;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.instanceOf;
 
 public class SpanFirstQueryBuilderTests extends AbstractQueryTestCase<SpanFirstQueryBuilder> {
@@ -59,7 +60,7 @@ public class SpanFirstQueryBuilderTests extends AbstractQueryTestCase<SpanFirstQ
             builder.endObject();
 
             ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(Strings.toString(builder)));
-            assertTrue(e.getMessage().contains("spanFirst must have [end] set"));
+            assertTrue(e.getMessage().contains("span_first must have [end] set"));
         }
         {
             XContentBuilder builder = XContentFactory.jsonBuilder();
@@ -70,7 +71,7 @@ public class SpanFirstQueryBuilderTests extends AbstractQueryTestCase<SpanFirstQ
             builder.endObject();
 
             ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(Strings.toString(builder)));
-            assertTrue(e.getMessage().contains("spanFirst must have [match] span query clause"));
+            assertTrue(e.getMessage().contains("span_first must have [match] span query clause"));
         }
     }
 
@@ -97,4 +98,27 @@ public class SpanFirstQueryBuilderTests extends AbstractQueryTestCase<SpanFirstQ
         assertEquals(json, 3, parsed.end());
         assertEquals(json, "kimchy", ((SpanTermQueryBuilder) parsed.innerQuery()).value());
     }
+
+
+    public void testFromJsonWithNonDefaultBoostInMatchQuery() {
+        String json =
+                "{\n" +
+                "  \"span_first\" : {\n" +
+                "    \"match\" : {\n" +
+                "      \"span_term\" : {\n" +
+                "        \"user\" : {\n" +
+                "          \"value\" : \"kimchy\",\n" +
+                "          \"boost\" : 2.0\n" +
+                "        }\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"end\" : 3,\n" +
+                "    \"boost\" : 1.0\n" +
+                "  }\n" +
+                "}";
+
+        Exception exception = expectThrows(ParsingException.class, () -> parseQuery(json));
+        assertThat(exception.getMessage(),
+            equalTo("span_first [match] as a nested span clause can't have non-default boost value [2.0]"));
+    }
 }

+ 38 - 1
server/src/test/java/org/elasticsearch/index/query/SpanNearQueryBuilderTests.java

@@ -114,7 +114,7 @@ public class SpanNearQueryBuilderTests extends AbstractQueryTestCase<SpanNearQue
                 "    } ],\n" +
                 "    \"slop\" : 12,\n" +
                 "    \"in_order\" : false,\n" +
-                "    \"boost\" : 1.0\n" +
+                "    \"boost\" : 2.0\n" +
                 "  }\n" +
                 "}";
 
@@ -124,6 +124,7 @@ public class SpanNearQueryBuilderTests extends AbstractQueryTestCase<SpanNearQue
         assertEquals(json, 3, parsed.clauses().size());
         assertEquals(json, 12, parsed.slop());
         assertEquals(json, false, parsed.inOrder());
+        assertEquals(json, 2.0, parsed.boost(), 0.0);
     }
 
     public void testParsingSlopDefault() throws IOException {
@@ -187,4 +188,40 @@ public class SpanNearQueryBuilderTests extends AbstractQueryTestCase<SpanNearQue
         assertThat(e.getMessage(), containsString("[span_near] query does not support [collect_payloads]"));
     }
 
+    public void testFromJsonWithNonDefaultBoostInInnerQuery() {
+        String json =
+                "{\n" +
+                "  \"span_near\" : {\n" +
+                "    \"clauses\" : [ {\n" +
+                "      \"span_term\" : {\n" +
+                "        \"field\" : {\n" +
+                "          \"value\" : \"value1\",\n" +
+                "          \"boost\" : 2.0\n" +
+                "        }\n" +
+                "      }\n" +
+                "    }, {\n" +
+                "      \"span_term\" : {\n" +
+                "        \"field\" : {\n" +
+                "          \"value\" : \"value2\",\n" +
+                "          \"boost\" : 1.0\n" +
+                "        }\n" +
+                "      }\n" +
+                "    }, {\n" +
+                "      \"span_term\" : {\n" +
+                "        \"field\" : {\n" +
+                "          \"value\" : \"value3\",\n" +
+                "          \"boost\" : 1.0\n" +
+                "        }\n" +
+                "      }\n" +
+                "    } ],\n" +
+                "    \"slop\" : 12,\n" +
+                "    \"in_order\" : false,\n" +
+                "    \"boost\" : 1.0\n" +
+                "  }\n" +
+                "}";
+
+        Exception exception = expectThrows(ParsingException.class, () -> parseQuery(json));
+        assertThat(exception.getMessage(),
+            equalTo("span_near [clauses] as a nested span clause can't have non-default boost value [2.0]"));
+    }
 }

+ 96 - 4
server/src/test/java/org/elasticsearch/index/query/SpanNotQueryBuilderTests.java

@@ -132,7 +132,7 @@ public class SpanNotQueryBuilderTests extends AbstractQueryTestCase<SpanNotQuery
             builder.endObject();
 
             ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(Strings.toString(builder)));
-            assertThat(e.getDetailedMessage(), containsString("spanNot must have [include]"));
+            assertThat(e.getDetailedMessage(), containsString("span_not must have [include]"));
         }
         {
             XContentBuilder builder = XContentFactory.jsonBuilder();
@@ -146,7 +146,7 @@ public class SpanNotQueryBuilderTests extends AbstractQueryTestCase<SpanNotQuery
             builder.endObject();
 
             ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(Strings.toString(builder)));
-            assertThat(e.getDetailedMessage(), containsString("spanNot must have [exclude]"));
+            assertThat(e.getDetailedMessage(), containsString("span_not must have [exclude]"));
         }
         {
             XContentBuilder builder = XContentFactory.jsonBuilder();
@@ -163,7 +163,7 @@ public class SpanNotQueryBuilderTests extends AbstractQueryTestCase<SpanNotQuery
             builder.endObject();
 
             ParsingException e = expectThrows(ParsingException.class, () -> parseQuery(Strings.toString(builder)));
-            assertThat(e.getDetailedMessage(), containsString("spanNot can either use [dist] or [pre] & [post] (or none)"));
+            assertThat(e.getDetailedMessage(), containsString("span_not can either use [dist] or [pre] & [post] (or none)"));
         }
     }
 
@@ -203,7 +203,7 @@ public class SpanNotQueryBuilderTests extends AbstractQueryTestCase<SpanNotQuery
                 "    },\n" +
                 "    \"pre\" : 0,\n" +
                 "    \"post\" : 0,\n" +
-                "    \"boost\" : 1.0\n" +
+                "    \"boost\" : 2.0\n" +
                 "  }\n" +
                 "}";
 
@@ -212,5 +212,97 @@ public class SpanNotQueryBuilderTests extends AbstractQueryTestCase<SpanNotQuery
 
         assertEquals(json, "hoya", ((SpanTermQueryBuilder) parsed.includeQuery()).value());
         assertEquals(json, 2, ((SpanNearQueryBuilder) parsed.excludeQuery()).clauses().size());
+        assertEquals(json, 2.0, parsed.boost(), 0.0);
+    }
+
+    public void testFromJsonWithNonDefaultBoostInIncludeQuery() {
+        String json =
+                "{\n" +
+                "  \"span_not\" : {\n" +
+                "    \"exclude\" : {\n" +
+                "      \"span_term\" : {\n" +
+                "        \"field1\" : {\n" +
+                "          \"value\" : \"hoya\",\n" +
+                "          \"boost\" : 1.0\n" +
+                "        }\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"include\" : {\n" +
+                "      \"span_near\" : {\n" +
+                "        \"clauses\" : [ {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"la\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        }, {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"hoya\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        } ],\n" +
+                "        \"slop\" : 0,\n" +
+                "        \"in_order\" : true,\n" +
+                "        \"boost\" : 2.0\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"pre\" : 0,\n" +
+                "    \"post\" : 0,\n" +
+                "    \"boost\" : 1.0\n" +
+                "  }\n" +
+                "}";
+
+        Exception exception = expectThrows(ParsingException.class, () -> parseQuery(json));
+        assertThat(exception.getMessage(),
+            equalTo("span_not [include] as a nested span clause can't have non-default boost value [2.0]"));
+    }
+
+
+    public void testFromJsonWithNonDefaultBoostInExcludeQuery() {
+        String json =
+                "{\n" +
+                "  \"span_not\" : {\n" +
+                "    \"include\" : {\n" +
+                "      \"span_term\" : {\n" +
+                "        \"field1\" : {\n" +
+                "          \"value\" : \"hoya\",\n" +
+                "          \"boost\" : 1.0\n" +
+                "        }\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"exclude\" : {\n" +
+                "      \"span_near\" : {\n" +
+                "        \"clauses\" : [ {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"la\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        }, {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"hoya\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        } ],\n" +
+                "        \"slop\" : 0,\n" +
+                "        \"in_order\" : true,\n" +
+                "        \"boost\" : 2.0\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"pre\" : 0,\n" +
+                "    \"post\" : 0,\n" +
+                "    \"boost\" : 1.0\n" +
+                "  }\n" +
+                "}";
+
+        Exception exception = expectThrows(ParsingException.class, () -> parseQuery(json));
+        assertThat(exception.getMessage(),
+            equalTo("span_not [exclude] as a nested span clause can't have non-default boost value [2.0]"));
     }
 }

+ 24 - 1
server/src/test/java/org/elasticsearch/index/query/SpanOrQueryBuilderTests.java

@@ -22,6 +22,7 @@ package org.elasticsearch.index.query;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.spans.SpanOrQuery;
 import org.apache.lucene.search.spans.SpanQuery;
+import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.search.internal.SearchContext;
 import org.elasticsearch.test.AbstractQueryTestCase;
 
@@ -94,7 +95,7 @@ public class SpanOrQueryBuilderTests extends AbstractQueryTestCase<SpanOrQueryBu
                 "        }\n" +
                 "      }\n" +
                 "    } ],\n" +
-                "    \"boost\" : 1.0\n" +
+                "    \"boost\" : 2.0\n" +
                 "  }\n" +
                 "}";
 
@@ -102,5 +103,27 @@ public class SpanOrQueryBuilderTests extends AbstractQueryTestCase<SpanOrQueryBu
         checkGeneratedJson(json, parsed);
 
         assertEquals(json, 3, parsed.clauses().size());
+        assertEquals(json, 2.0, parsed.boost(), 0.0);
+    }
+
+    public void testFromJsonWithNonDefaultBoostInInnerQuery() {
+        String json =
+                "{\n" +
+                "  \"span_or\" : {\n" +
+                "    \"clauses\" : [ {\n" +
+                "      \"span_term\" : {\n" +
+                "        \"field\" : {\n" +
+                "          \"value\" : \"value1\",\n" +
+                "          \"boost\" : 2.0\n" +
+                "        }\n" +
+                "      }\n" +
+                "    } ],\n" +
+                "    \"boost\" : 1.0\n" +
+                "  }\n" +
+                "}";
+
+        Exception exception = expectThrows(ParsingException.class, () -> parseQuery(json));
+        assertThat(exception.getMessage(),
+            equalTo("span_or [clauses] as a nested span clause can't have non-default boost value [2.0]"));
     }
 }

+ 3 - 6
server/src/test/java/org/elasticsearch/index/query/SpanTermQueryBuilderTests.java

@@ -76,19 +76,16 @@ public class SpanTermQueryBuilderTests extends AbstractTermQueryTestCase<SpanTer
     }
 
     /**
-     * @param amount the number of clauses that will be returned
-     * @return an array of random {@link SpanTermQueryBuilder} with same field name
+     * @param amount a number of clauses that will be returned
+     * @return the array of random {@link SpanTermQueryBuilder} with same field name
      */
     public SpanTermQueryBuilder[] createSpanTermQueryBuilders(int amount) {
         SpanTermQueryBuilder[] clauses = new SpanTermQueryBuilder[amount];
-        SpanTermQueryBuilder first = createTestQueryBuilder();
+        SpanTermQueryBuilder first = createTestQueryBuilder(false, true);
         clauses[0] = first;
         for (int i = 1; i < amount; i++) {
             // we need same field name in all clauses, so we only randomize value
             SpanTermQueryBuilder spanTermQuery = new SpanTermQueryBuilder(first.fieldName(), getRandomValueForFieldName(first.fieldName()));
-            if (randomBoolean()) {
-                spanTermQuery.boost(2.0f / randomIntBetween(1, 20));
-            }
             if (randomBoolean()) {
                 spanTermQuery.queryName(randomAlphaOfLengthBetween(1, 10));
             }

+ 90 - 1
server/src/test/java/org/elasticsearch/index/query/SpanWithinQueryBuilderTests.java

@@ -21,11 +21,13 @@ package org.elasticsearch.index.query;
 
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.spans.SpanWithinQuery;
+import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.search.internal.SearchContext;
 import org.elasticsearch.test.AbstractQueryTestCase;
 
 import java.io.IOException;
 
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.instanceOf;
 
 public class SpanWithinQueryBuilderTests extends AbstractQueryTestCase<SpanWithinQueryBuilder> {
@@ -80,7 +82,7 @@ public class SpanWithinQueryBuilderTests extends AbstractQueryTestCase<SpanWithi
                 "        }\n" +
                 "      }\n" +
                 "    },\n" +
-                "    \"boost\" : 1.0\n" +
+                "    \"boost\" : 2.0\n" +
                 "  }\n" +
                 "}";
 
@@ -89,5 +91,92 @@ public class SpanWithinQueryBuilderTests extends AbstractQueryTestCase<SpanWithi
 
         assertEquals(json, "foo", ((SpanTermQueryBuilder) parsed.littleQuery()).value());
         assertEquals(json, 2, ((SpanNearQueryBuilder) parsed.bigQuery()).clauses().size());
+        assertEquals(json, 2.0, parsed.boost(), 0.0);
+    }
+
+    public void testFromJsonWithNonDefaultBoostInBigQuery() {
+        String json =
+                "{\n" +
+                "  \"span_within\" : {\n" +
+                "    \"big\" : {\n" +
+                "      \"span_near\" : {\n" +
+                "        \"clauses\" : [ {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"bar\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        }, {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"baz\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        } ],\n" +
+                "        \"slop\" : 5,\n" +
+                "        \"in_order\" : true,\n" +
+                "        \"boost\" : 2.0\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"little\" : {\n" +
+                "      \"span_term\" : {\n" +
+                "        \"field1\" : {\n" +
+                "          \"value\" : \"foo\",\n" +
+                "          \"boost\" : 1.0\n" +
+                "        }\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"boost\" : 1.0\n" +
+                "  }\n" +
+                "}";
+
+        Exception exception = expectThrows(ParsingException.class, () -> parseQuery(json));
+        assertThat(exception.getMessage(),
+            equalTo("span_within [big] as a nested span clause can't have non-default boost value [2.0]"));
+    }
+
+    public void testFromJsonWithNonDefaultBoostInLittleQuery() {
+        String json =
+                "{\n" +
+                "  \"span_within\" : {\n" +
+                "    \"little\" : {\n" +
+                "      \"span_near\" : {\n" +
+                "        \"clauses\" : [ {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"bar\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        }, {\n" +
+                "          \"span_term\" : {\n" +
+                "            \"field1\" : {\n" +
+                "              \"value\" : \"baz\",\n" +
+                "              \"boost\" : 1.0\n" +
+                "            }\n" +
+                "          }\n" +
+                "        } ],\n" +
+                "        \"slop\" : 5,\n" +
+                "        \"in_order\" : true,\n" +
+                "        \"boost\" : 2.0\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"big\" : {\n" +
+                "      \"span_term\" : {\n" +
+                "        \"field1\" : {\n" +
+                "          \"value\" : \"foo\",\n" +
+                "          \"boost\" : 1.0\n" +
+                "        }\n" +
+                "      }\n" +
+                "    },\n" +
+                "    \"boost\" : 1.0\n" +
+                "  }\n" +
+                "}";
+
+        Exception exception = expectThrows(ParsingException.class, () -> parseQuery(json));
+        assertThat(exception.getMessage(),
+            equalTo("span_within [little] as a nested span clause can't have non-default boost value [2.0]"));
     }
 }

+ 6 - 1
server/src/test/java/org/elasticsearch/index/query/WrapperQueryBuilderTests.java

@@ -39,7 +39,12 @@ import java.io.UnsupportedEncodingException;
 public class WrapperQueryBuilderTests extends AbstractQueryTestCase<WrapperQueryBuilder> {
 
     @Override
-    protected boolean supportsBoostAndQueryName() {
+    protected boolean supportsBoost() {
+        return false;
+    }
+
+    @Override
+    protected boolean supportsQueryName() {
         return false;
     }
 

+ 25 - 15
test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java

@@ -20,7 +20,6 @@
 package org.elasticsearch.test;
 
 import com.fasterxml.jackson.core.io.JsonStringEncoder;
-
 import org.apache.lucene.search.BoostQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
@@ -83,15 +82,16 @@ public abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
     private static final int NUMBER_OF_TESTQUERIES = 20;
 
     public final QB createTestQueryBuilder() {
+        return createTestQueryBuilder(supportsBoost(), supportsQueryName());
+    }
+
+    public final QB createTestQueryBuilder(boolean supportsBoost, boolean supportsQueryName) {
         QB query = doCreateTestQueryBuilder();
-        //we should not set boost and query name for queries that don't parse it
-        if (supportsBoostAndQueryName()) {
-            if (randomBoolean()) {
-                query.boost(2.0f / randomIntBetween(1, 20));
-            }
-            if (randomBoolean()) {
-                query.queryName(createUniqueRandomName());
-            }
+        if (supportsBoost && randomBoolean()) {
+            query.boost(2.0f / randomIntBetween(1, 20));
+        }
+        if (supportsQueryName && randomBoolean()) {
+            query.queryName(createUniqueRandomName());
         }
         return query;
     }
@@ -460,7 +460,7 @@ public abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
                         rewrite(secondLuceneQuery), rewrite(firstLuceneQuery));
             }
 
-            if (supportsBoostAndQueryName()) {
+            if (supportsBoost()) {
                 secondQuery.boost(firstQuery.boost() + 1f + randomFloat());
                 Query thirdLuceneQuery = rewriteQuery(secondQuery, context).toQuery(context);
                 assertNotEquals("modifying the boost doesn't affect the corresponding lucene query", rewrite(firstLuceneQuery),
@@ -487,12 +487,22 @@ public abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
     }
 
     /**
-     * Few queries allow you to set the boost and queryName on the java api, although the corresponding parser
-     * doesn't parse them as they are not supported. This method allows to disable boost and queryName related tests for those queries.
-     * Those queries are easy to identify: their parsers don't parse `boost` and `_name` as they don't apply to the specific query:
-     * wrapper query and match_none
+     * Few queries allow you to set the boost on the Java API, although the corresponding parser
+     * doesn't parse it as it isn't supported. This method allows to disable boost related tests for those queries.
+     * Those queries are easy to identify: their parsers don't parse {@code boost} as they don't apply to the specific query:
+     * wrapper query and {@code match_none}.
+     */
+    protected boolean supportsBoost() {
+        return true;
+    }
+
+    /**
+     * Few queries allow you to set the query name on the Java API, although the corresponding parser
+     * doesn't parse it as it isn't supported. This method allows to disable query name related tests for those queries.
+     * Those queries are easy to identify: their parsers don't parse {@code _name} as they don't apply to the specific query:
+     * wrapper query and {@code match_none}.
      */
-    protected boolean supportsBoostAndQueryName() {
+    protected boolean supportsQueryName() {
         return true;
     }