diff --git a/src/main/java/org/elasticsearch/index/query/SpanNotQueryBuilder.java b/src/main/java/org/elasticsearch/index/query/SpanNotQueryBuilder.java index ce9d53fe04cd1..e42260b7f9a90 100644 --- a/src/main/java/org/elasticsearch/index/query/SpanNotQueryBuilder.java +++ b/src/main/java/org/elasticsearch/index/query/SpanNotQueryBuilder.java @@ -29,11 +29,19 @@ */ public class SpanNotQueryBuilder extends BaseQueryBuilder implements SpanQueryBuilder, BoostableQueryBuilder { + public static final int NOT_SET = -1; + private SpanQueryBuilder include; private SpanQueryBuilder exclude; - private float boost = -1; + private int dist = NOT_SET; + + private int pre = NOT_SET; + + private int post = NOT_SET; + + private float boost = NOT_SET; private String queryName; @@ -47,6 +55,21 @@ public SpanNotQueryBuilder exclude(SpanQueryBuilder exclude) { return this; } + public SpanNotQueryBuilder dist(int dist) { + this.dist = dist; + return this; + } + + public SpanNotQueryBuilder pre(int pre) { + this.pre = (pre >=0) ? pre : 0; + return this; + } + + public SpanNotQueryBuilder post(int post) { + this.post = (post >= 0) ? post : 0; + return this; + } + public SpanNotQueryBuilder boost(float boost) { this.boost = boost; return this; @@ -54,6 +77,8 @@ public SpanNotQueryBuilder boost(float boost) { /** * Sets the query name for the filter that can be used when searching for matched_filters per hit. + * @param queryName The query name + * @return this */ public SpanNotQueryBuilder queryName(String queryName) { this.queryName = queryName; @@ -68,12 +93,33 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep if (exclude == null) { throw new ElasticsearchIllegalArgumentException("Must specify exclude when using spanNot query"); } + + if (dist != NOT_SET && (pre != NOT_SET || post != NOT_SET)) { + throw new ElasticsearchIllegalArgumentException("spanNot can either use [dist] or [pre] & [post] (or none)"); + } + + // set appropriate defaults + if (pre != NOT_SET && post == NOT_SET) { + post = 0; + } else if (pre == NOT_SET && post != NOT_SET){ + pre = 0; + } + builder.startObject(SpanNotQueryParser.NAME); builder.field("include"); include.toXContent(builder, params); builder.field("exclude"); exclude.toXContent(builder, params); - if (boost != -1) { + if (dist != NOT_SET) { + builder.field("dist", dist); + } + if (pre != NOT_SET) { + builder.field("pre", pre); + } + if (post != NOT_SET) { + builder.field("post", post); + } + if (boost != NOT_SET) { builder.field("boost", boost); } if (queryName != null) { diff --git a/src/main/java/org/elasticsearch/index/query/SpanNotQueryParser.java b/src/main/java/org/elasticsearch/index/query/SpanNotQueryParser.java index 269973e272f1f..b099db72cb4cc 100644 --- a/src/main/java/org/elasticsearch/index/query/SpanNotQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/SpanNotQueryParser.java @@ -35,6 +35,8 @@ public class SpanNotQueryParser implements QueryParser { public static final String NAME = "span_not"; + public static final int NOT_SET = -1; + @Inject public SpanNotQueryParser() { } @@ -52,6 +54,11 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars SpanQuery include = null; SpanQuery exclude = null; + + int dist = NOT_SET; + int pre = NOT_SET; + int post = NOT_SET; + String queryName = null; String currentFieldName = null; @@ -76,7 +83,13 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars throw new QueryParsingException(parseContext.index(), "[span_not] query does not support [" + currentFieldName + "]"); } } else { - if ("boost".equals(currentFieldName)) { + if ("dist".equals(currentFieldName)) { + dist = parser.intValue(); + } else if ("pre".equals(currentFieldName)) { + pre = parser.intValue(); + } else if ("post".equals(currentFieldName)) { + post = parser.intValue(); + } else if ("boost".equals(currentFieldName)) { boost = parser.floatValue(); } else if ("_name".equals(currentFieldName)) { queryName = parser.text(); @@ -91,8 +104,19 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars if (exclude == null) { throw new QueryParsingException(parseContext.index(), "spanNot must have [exclude] span query clause"); } + if (dist != NOT_SET && (pre != NOT_SET || post != NOT_SET)) { + throw new QueryParsingException(parseContext.index(), "spanNot can either use [dist] or [pre] & [post] (or none)"); + } + + SpanNotQuery query; + if (pre != NOT_SET && post != NOT_SET) { + query = new SpanNotQuery(include, exclude, pre, post); + } else if (dist != NOT_SET) { + query = new SpanNotQuery(include, exclude, dist); + } else { + query = new SpanNotQuery(include, exclude); + } - SpanNotQuery query = new SpanNotQuery(include, exclude); query.setBoost(boost); if (queryName != null) { parseContext.addNamedQuery(queryName, query); diff --git a/src/test/java/org/elasticsearch/search/query/SimpleQueryTests.java b/src/test/java/org/elasticsearch/search/query/SimpleQueryTests.java index 126a20abab886..00c7488d233e5 100644 --- a/src/test/java/org/elasticsearch/search/query/SimpleQueryTests.java +++ b/src/test/java/org/elasticsearch/search/query/SimpleQueryTests.java @@ -1526,6 +1526,48 @@ public void testSpanMultiTermQuery() throws ElasticsearchException, IOException assertHitCount(response, 3); } + @Test + public void testSpanNot() throws ElasticsearchException, IOException, ExecutionException, InterruptedException { + createIndex("test"); + ensureGreen(); + + client().prepareIndex("test", "test", "1").setSource("description", "the quick brown fox jumped over the lazy dog").get(); + client().prepareIndex("test", "test", "2").setSource("description", "the quick black fox leaped over the sleeping dog").get(); + refresh(); + + SearchResponse searchResponse = client().prepareSearch("test") + .setQuery(spanNotQuery().include(spanNearQuery() + .clause(QueryBuilders.spanTermQuery("description", "quick")) + .clause(QueryBuilders.spanTermQuery("description", "fox")).slop(1)).exclude(spanTermQuery("description", "brown"))).get(); + assertHitCount(searchResponse, 1l); + + searchResponse = client().prepareSearch("test") + .setQuery(spanNotQuery().include(spanNearQuery() + .clause(QueryBuilders.spanTermQuery("description", "quick")) + .clause(QueryBuilders.spanTermQuery("description", "fox")).slop(1)).exclude(spanTermQuery("description", "sleeping")).dist(5)).get(); + assertHitCount(searchResponse, 1l); + + searchResponse = client().prepareSearch("test") + .setQuery(spanNotQuery().include(spanNearQuery() + .clause(QueryBuilders.spanTermQuery("description", "quick")) + .clause(QueryBuilders.spanTermQuery("description", "fox")).slop(1)).exclude(spanTermQuery("description", "jumped")).pre(1).post(1)).get(); + assertHitCount(searchResponse, 1l); + + SearchRequestBuilder builder = client().prepareSearch("test") + .setQuery(spanNotQuery().include(spanNearQuery() + .clause(QueryBuilders.spanTermQuery("description", "quick")) + .clause(QueryBuilders.spanTermQuery("description", "fox")).slop(1)).exclude(spanTermQuery("description", "jumped")).dist(2).pre(2)); + boolean caught = false; + try { + builder.execute(); + } catch (ElasticsearchException e) { + assertTrue("ElasticsearchIllegalArgumentException should have been caught", e.getDetailedMessage().endsWith("spanNot can either use [dist] or [pre] & [post] (or none)")); + caught = true; + } finally { + assertTrue("ElasticsearchIllegalArgumentException should have been caught", caught); + } + } + @Test public void testSimpleDFSQuery() throws ElasticsearchException, IOException { assertAcked(prepareCreate("test")