1
0
Эх сурвалжийг харах

Add span within/containing queries.

Expose new span queries from https://issues.apache.org/jira/browse/LUCENE-6083

Within returns matches from 'little' that are enclosed inside of a match from 'big'.
Containing returns matches from 'big' that enclose matches from 'little'.
Robert Muir 10 жил өмнө
parent
commit
aade6194b7

+ 4 - 0
docs/reference/query-dsl/queries.asciidoc

@@ -52,6 +52,8 @@ include::queries/range-query.asciidoc[]
 
 include::queries/regexp-query.asciidoc[]
 
+include::queries/span-containing-query.asciidoc[]
+
 include::queries/span-first-query.asciidoc[]
 
 include::queries/span-multi-term-query.asciidoc[]
@@ -64,6 +66,8 @@ include::queries/span-or-query.asciidoc[]
 
 include::queries/span-term-query.asciidoc[]
 
+include::queries/span-within-query.asciidoc[]
+
 include::queries/term-query.asciidoc[]
 
 include::queries/terms-query.asciidoc[]

+ 29 - 0
docs/reference/query-dsl/queries/span-containing-query.asciidoc

@@ -0,0 +1,29 @@
+[[query-dsl-span-containing-query]]
+=== Span Containing Query
+
+Returns matches which enclose another span query. The span within
+query maps to Lucene `SpanContainingQuery`. Here is an example:
+
+[source,js]
+--------------------------------------------------
+{
+    "span_containing" : {
+        "little" : {
+            "span_term" : { "field1" : "foo" }
+        },
+        "big" : {
+            "span_near" : { 
+                "clauses" : [
+                    { "span_term" : { "field1" : "bar" } },
+                    { "span_term" : { "field1" : "baz" } }
+                ],
+                "slop" : 5,
+                "in_order" : true
+            }
+        }
+    }
+}
+--------------------------------------------------
+
+The `big` and `little` clauses can be any span type query. Matching
+spans from `big` that contain matches from `little` are returned.

+ 29 - 0
docs/reference/query-dsl/queries/span-within-query.asciidoc

@@ -0,0 +1,29 @@
+[[query-dsl-span-within-query]]
+=== Span Within Query
+
+Returns matches which are enclosed inside another span query. The span within
+query maps to Lucene `SpanWithinQuery`. Here is an example:
+
+[source,js]
+--------------------------------------------------
+{
+    "span_within" : {
+        "little" : {
+            "span_term" : { "field1" : "foo" }
+        },
+        "big" : {
+            "span_near" : { 
+                "clauses" : [
+                    { "span_term" : { "field1" : "bar" } },
+                    { "span_term" : { "field1" : "baz" } }
+                ],
+                "slop" : 5,
+                "in_order" : true
+            }
+        }
+    }
+}
+--------------------------------------------------
+
+The `big` and `little` clauses can be any span type query. Matching
+spans from `little` that are enclosed within `big` are returned.

+ 10 - 0
src/main/java/org/elasticsearch/index/query/QueryBuilders.java

@@ -318,6 +318,16 @@ public abstract class QueryBuilders {
         return new SpanOrQueryBuilder();
     }
 
+    /** Creates a new {@code span_within} builder. */
+    public static SpanWithinQueryBuilder spanWithinQuery() {
+        return new SpanWithinQueryBuilder();
+    }
+
+    /** Creates a new {@code span_containing} builder. */
+    public static SpanContainingQueryBuilder spanContainingQuery() {
+        return new SpanContainingQueryBuilder();
+    }
+
     /**
      * Creates a {@link SpanQueryBuilder} which allows having a sub query
      * which implements {@link MultiTermQueryBuilder}. This is useful for

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

@@ -0,0 +1,92 @@
+/*
+ * 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.index.query;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+/**
+ * Builder for {@link SpanContainingQuery}.
+ */
+public class SpanContainingQueryBuilder extends BaseQueryBuilder implements SpanQueryBuilder, BoostableQueryBuilder<SpanContainingQueryBuilder> {
+
+    private SpanQueryBuilder big;
+    private SpanQueryBuilder little;
+    private float boost = -1;
+    private String queryName;
+
+    /** 
+     * Sets the little clause, it must be contained within {@code big} for a match.
+     */
+    public SpanContainingQueryBuilder little(SpanQueryBuilder clause) {
+        this.little = clause;
+        return this;
+    }
+
+    /** 
+     * Sets the big clause, it must enclose {@code little} for a match.
+     */
+    public SpanContainingQueryBuilder big(SpanQueryBuilder clause) {
+        this.big = clause;
+        return this;
+    }
+
+    @Override
+    public SpanContainingQueryBuilder boost(float boost) {
+        this.boost = boost;
+        return this;
+    }
+
+    /**
+     * Sets the query name for the filter that can be used when searching for matched_filters per hit.
+     */
+    public SpanContainingQueryBuilder queryName(String queryName) {
+        this.queryName = queryName;
+        return this;
+    }
+
+    @Override
+    protected void doXContent(XContentBuilder builder, Params params) throws IOException {
+        if (big == null) {
+            throw new IllegalArgumentException("Must specify big clause when building a span_containing query");
+        }
+        if (little == null) {
+            throw new IllegalArgumentException("Must specify little clause when building a span_containing query");
+        }
+        builder.startObject(SpanContainingQueryParser.NAME);
+
+        builder.field("big");
+        big.toXContent(builder, params);
+
+        builder.field("little");
+        little.toXContent(builder, params);
+
+        if (boost != -1) {
+            builder.field("boost", boost);
+        }
+
+        if (queryName != null) {
+            builder.field("_name", queryName);
+        }
+
+        builder.endObject();
+    }
+}

+ 100 - 0
src/main/java/org/elasticsearch/index/query/SpanContainingQueryParser.java

@@ -0,0 +1,100 @@
+/*
+ * 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.index.query;
+
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.spans.SpanContainingQuery;
+import org.apache.lucene.search.spans.SpanQuery;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+
+/**
+ * Parser for {@link SpanContainingQuery}
+ */
+public class SpanContainingQueryParser implements QueryParser {
+
+    public static final String NAME = "span_containing";
+
+    @Inject
+    public SpanContainingQueryParser() {
+    }
+
+    @Override
+    public String[] names() {
+        return new String[]{NAME, Strings.toCamelCase(NAME)};
+    }
+
+    @Override
+    public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
+        XContentParser parser = parseContext.parser();
+
+        float boost = 1.0f;
+        String queryName = null;
+        SpanQuery big = null;
+        SpanQuery little = null;
+
+        String currentFieldName = null;
+        XContentParser.Token token;
+        while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+            if (token == XContentParser.Token.FIELD_NAME) {
+                currentFieldName = parser.currentName();
+            } else if (token == XContentParser.Token.START_OBJECT) {
+                if ("big".equals(currentFieldName)) {
+                    Query query = parseContext.parseInnerQuery();
+                    if (!(query instanceof SpanQuery)) {
+                        throw new QueryParsingException(parseContext, "span_containing [big] must be of type span query");
+                    }
+                    big = (SpanQuery) query;
+                } else if ("little".equals(currentFieldName)) {
+                    Query query = parseContext.parseInnerQuery();
+                    if (!(query instanceof SpanQuery)) {
+                        throw new QueryParsingException(parseContext, "span_containing [little] must be of type span query");
+                    }
+                    little = (SpanQuery) query;
+                } else {
+                    throw new QueryParsingException(parseContext, "[span_containing] query does not support [" + currentFieldName + "]");
+                }
+            } else if ("boost".equals(currentFieldName)) {
+                boost = parser.floatValue();
+            } else if ("_name".equals(currentFieldName)) {
+                queryName = parser.text();
+            } else {
+                throw new QueryParsingException(parseContext, "[span_containing] query does not support [" + currentFieldName + "]");
+            }
+        }        
+        
+        if (big == null) {
+            throw new QueryParsingException(parseContext, "span_containing must include [big]");
+        }
+        if (little == null) {
+            throw new QueryParsingException(parseContext, "span_containing must include [little]");
+        }
+
+        Query query = new SpanContainingQuery(big, little);
+        query.setBoost(boost);
+        if (queryName != null) {
+            parseContext.addNamedQuery(queryName, query);
+        }
+        return query;
+    }
+}

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

@@ -0,0 +1,92 @@
+/*
+ * 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.index.query;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+/**
+ * Builder for {@link SpanWithinQuery}.
+ */
+public class SpanWithinQueryBuilder extends BaseQueryBuilder implements SpanQueryBuilder, BoostableQueryBuilder<SpanWithinQueryBuilder> {
+
+    private SpanQueryBuilder big;
+    private SpanQueryBuilder little;
+    private float boost = -1;
+    private String queryName;
+
+    /** 
+     * Sets the little clause, it must be contained within {@code big} for a match.
+     */
+    public SpanWithinQueryBuilder little(SpanQueryBuilder clause) {
+        this.little = clause;
+        return this;
+    }
+
+    /** 
+     * Sets the big clause, it must enclose {@code little} for a match.
+     */
+    public SpanWithinQueryBuilder big(SpanQueryBuilder clause) {
+        this.big = clause;
+        return this;
+    }
+
+    @Override
+    public SpanWithinQueryBuilder boost(float boost) {
+        this.boost = boost;
+        return this;
+    }
+
+    /**
+     * Sets the query name for the filter that can be used when searching for matched_filters per hit.
+     */
+    public SpanWithinQueryBuilder queryName(String queryName) {
+        this.queryName = queryName;
+        return this;
+    }
+
+    @Override
+    protected void doXContent(XContentBuilder builder, Params params) throws IOException {
+        if (big == null) {
+            throw new IllegalArgumentException("Must specify big clause when building a span_within query");
+        }
+        if (little == null) {
+            throw new IllegalArgumentException("Must specify little clause when building a span_within query");
+        }
+        builder.startObject(SpanWithinQueryParser.NAME);
+
+        builder.field("big");
+        big.toXContent(builder, params);
+
+        builder.field("little");
+        little.toXContent(builder, params);
+
+        if (boost != -1) {
+            builder.field("boost", boost);
+        }
+
+        if (queryName != null) {
+            builder.field("_name", queryName);
+        }
+
+        builder.endObject();
+    }
+}

+ 100 - 0
src/main/java/org/elasticsearch/index/query/SpanWithinQueryParser.java

@@ -0,0 +1,100 @@
+/*
+ * 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.index.query;
+
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.spans.SpanQuery;
+import org.apache.lucene.search.spans.SpanWithinQuery;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+
+/**
+ * Parser for {@link SpanWithinQuery}
+ */
+public class SpanWithinQueryParser implements QueryParser {
+
+    public static final String NAME = "span_within";
+
+    @Inject
+    public SpanWithinQueryParser() {
+    }
+
+    @Override
+    public String[] names() {
+        return new String[]{NAME, Strings.toCamelCase(NAME)};
+    }
+
+    @Override
+    public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
+        XContentParser parser = parseContext.parser();
+
+        float boost = 1.0f;
+        String queryName = null;
+        SpanQuery big = null;
+        SpanQuery little = null;
+
+        String currentFieldName = null;
+        XContentParser.Token token;
+        while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+            if (token == XContentParser.Token.FIELD_NAME) {
+                currentFieldName = parser.currentName();
+            } else if (token == XContentParser.Token.START_OBJECT) {
+                if ("big".equals(currentFieldName)) {
+                    Query query = parseContext.parseInnerQuery();
+                    if (query instanceof SpanQuery == false) {
+                        throw new QueryParsingException(parseContext, "span_within [big] must be of type span query");
+                    }
+                    big = (SpanQuery) query;
+                } else if ("little".equals(currentFieldName)) {
+                    Query query = parseContext.parseInnerQuery();
+                    if (query instanceof SpanQuery == false) {
+                        throw new QueryParsingException(parseContext, "span_within [little] must be of type span query");
+                    }
+                    little = (SpanQuery) query;
+                } else {
+                    throw new QueryParsingException(parseContext, "[span_within] query does not support [" + currentFieldName + "]");
+                }
+            } else if ("boost".equals(currentFieldName)) {
+                boost = parser.floatValue();
+            } else if ("_name".equals(currentFieldName)) {
+                queryName = parser.text();
+            } else {
+                throw new QueryParsingException(parseContext, "[span_within] query does not support [" + currentFieldName + "]");
+            }
+        }        
+        
+        if (big == null) {
+            throw new QueryParsingException(parseContext, "span_within must include [big]");
+        }
+        if (little == null) {
+            throw new QueryParsingException(parseContext, "span_within must include [little]");
+        }
+
+        Query query = new SpanWithinQuery(big, little);
+        query.setBoost(boost);
+        if (queryName != null) {
+            parseContext.addNamedQuery(queryName, query);
+        }
+        return query;
+    }
+}

+ 2 - 0
src/main/java/org/elasticsearch/indices/query/IndicesQueriesModule.java

@@ -89,6 +89,8 @@ public class IndicesQueriesModule extends AbstractModule {
         qpBinders.addBinding().to(ConstantScoreQueryParser.class).asEagerSingleton();
         qpBinders.addBinding().to(SpanTermQueryParser.class).asEagerSingleton();
         qpBinders.addBinding().to(SpanNotQueryParser.class).asEagerSingleton();
+        qpBinders.addBinding().to(SpanWithinQueryParser.class).asEagerSingleton();
+        qpBinders.addBinding().to(SpanContainingQueryParser.class).asEagerSingleton();
         qpBinders.addBinding().to(FieldMaskingSpanQueryParser.class).asEagerSingleton();
         qpBinders.addBinding().to(SpanFirstQueryParser.class).asEagerSingleton();
         qpBinders.addBinding().to(SpanNearQueryParser.class).asEagerSingleton();

+ 48 - 0
src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java

@@ -51,12 +51,14 @@ import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.TermRangeQuery;
 import org.apache.lucene.search.WildcardQuery;
 import org.apache.lucene.search.spans.FieldMaskingSpanQuery;
+import org.apache.lucene.search.spans.SpanContainingQuery;
 import org.apache.lucene.search.spans.SpanFirstQuery;
 import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper;
 import org.apache.lucene.search.spans.SpanNearQuery;
 import org.apache.lucene.search.spans.SpanNotQuery;
 import org.apache.lucene.search.spans.SpanOrQuery;
 import org.apache.lucene.search.spans.SpanTermQuery;
+import org.apache.lucene.search.spans.SpanWithinQuery;
 import org.apache.lucene.spatial.prefix.IntersectsPrefixTreeFilter;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.BytesRefBuilder;
@@ -131,11 +133,13 @@ import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
 import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
 import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
 import static org.elasticsearch.index.query.QueryBuilders.regexpQuery;
+import static org.elasticsearch.index.query.QueryBuilders.spanContainingQuery;
 import static org.elasticsearch.index.query.QueryBuilders.spanFirstQuery;
 import static org.elasticsearch.index.query.QueryBuilders.spanNearQuery;
 import static org.elasticsearch.index.query.QueryBuilders.spanNotQuery;
 import static org.elasticsearch.index.query.QueryBuilders.spanOrQuery;
 import static org.elasticsearch.index.query.QueryBuilders.spanTermQuery;
+import static org.elasticsearch.index.query.QueryBuilders.spanWithinQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
 import static org.elasticsearch.index.query.QueryBuilders.wildcardQuery;
@@ -1434,6 +1438,50 @@ public class SimpleIndexQueryParserTests extends ElasticsearchSingleNodeTest {
         assertThat(((SpanTermQuery) spanNotQuery.getExclude()).getTerm(), equalTo(new Term("age", longToPrefixCoded(35, 0))));
     }
 
+    @Test
+    public void testSpanWithinQueryBuilder() throws IOException {
+        IndexQueryParserService queryParser = queryParser();
+        Query expectedQuery = new SpanWithinQuery(new SpanTermQuery(new Term("age", longToPrefixCoded(34, 0))),
+                                                  new SpanTermQuery(new Term("age", longToPrefixCoded(35, 0))));
+        Query actualQuery = queryParser.parse(spanWithinQuery()
+                                              .big(spanTermQuery("age", 34))
+                                              .little(spanTermQuery("age", 35)))
+                                              .query();
+        assertEquals(expectedQuery, actualQuery);
+    }
+
+    @Test
+    public void testSpanWithinQueryParser() throws IOException {
+        IndexQueryParserService queryParser = queryParser();
+        Query expectedQuery = new SpanWithinQuery(new SpanTermQuery(new Term("age", longToPrefixCoded(34, 0))),
+                                                  new SpanTermQuery(new Term("age", longToPrefixCoded(35, 0))));
+        String queryText = copyToStringFromClasspath("/org/elasticsearch/index/query/spanWithin.json");
+        Query actualQuery = queryParser.parse(queryText).query();
+        assertEquals(expectedQuery, actualQuery);
+    }
+
+    @Test
+    public void testSpanContainingQueryBuilder() throws IOException {
+        IndexQueryParserService queryParser = queryParser();
+        Query expectedQuery = new SpanContainingQuery(new SpanTermQuery(new Term("age", longToPrefixCoded(34, 0))),
+                                                      new SpanTermQuery(new Term("age", longToPrefixCoded(35, 0))));
+        Query actualQuery = queryParser.parse(spanContainingQuery()
+                                              .big(spanTermQuery("age", 34))
+                                              .little(spanTermQuery("age", 35)))
+                                              .query();
+        assertEquals(expectedQuery, actualQuery);
+    }
+
+    @Test
+    public void testSpanContainingQueryParser() throws IOException {
+        IndexQueryParserService queryParser = queryParser();
+        Query expectedQuery = new SpanContainingQuery(new SpanTermQuery(new Term("age", longToPrefixCoded(34, 0))),
+                                                      new SpanTermQuery(new Term("age", longToPrefixCoded(35, 0))));
+        String queryText = copyToStringFromClasspath("/org/elasticsearch/index/query/spanContaining.json");
+        Query actualQuery = queryParser.parse(queryText).query();
+        assertEquals(expectedQuery, actualQuery);
+    }
+
     @Test
     public void testSpanFirstQueryBuilder() throws IOException {
         IndexQueryParserService queryParser = queryParser();

+ 14 - 0
src/test/java/org/elasticsearch/index/query/spanContaining.json

@@ -0,0 +1,14 @@
+{
+    span_containing:{
+        big:{
+            span_term:{
+                age:34
+            }
+        },
+        little:{
+            span_term:{
+                age:35
+            }
+        }
+    }
+}

+ 14 - 0
src/test/java/org/elasticsearch/index/query/spanWithin.json

@@ -0,0 +1,14 @@
+{
+    span_within:{
+        big:{
+            span_term:{
+                age:34
+            }
+        },
+        little:{
+            span_term:{
+                age:35
+            }
+        }
+    }
+}