Support SpanMultiTerm, closes #2610, #2400

This adds support for lucene span multi term queries. This lucene query
allows users to form complicated queries such as wildcards or prefix
queries embedded within span queries.
This commit is contained in:
Anton Hägerstrand 2013-01-30 21:42:48 -08:00 committed by Alexander Reelsen
parent ed289dc6c7
commit e30aa6b221
16 changed files with 255 additions and 5 deletions

View File

@ -28,7 +28,7 @@ import java.io.IOException;
*
*
*/
public class FuzzyQueryBuilder extends BaseQueryBuilder implements BoostableQueryBuilder<FuzzyQueryBuilder> {
public class FuzzyQueryBuilder extends BaseQueryBuilder implements MultiTermQueryBuilder, BoostableQueryBuilder<FuzzyQueryBuilder> {
private final String name;

View File

@ -0,0 +1,5 @@
package org.elasticsearch.index.query;
public interface MultiTermQueryBuilder extends QueryBuilder{
}

View File

@ -28,7 +28,7 @@ import java.io.IOException;
*
*
*/
public class PrefixQueryBuilder extends BaseQueryBuilder implements BoostableQueryBuilder<PrefixQueryBuilder> {
public class PrefixQueryBuilder extends BaseQueryBuilder implements MultiTermQueryBuilder, BoostableQueryBuilder<PrefixQueryBuilder> {
private final String name;

View File

@ -441,6 +441,20 @@ public abstract class QueryBuilders {
return new SpanOrQueryBuilder();
}
/**
* Creates a {@link SpanQueryBuilder} which allows having a sub query
* which implements {@link MultiTermQueryBuilder}. This is useful for
* having e.g. wildcard or fuzzy queries inside spans.
*
* @param multiTermQueryBuilder The {@link MultiTermQueryBuilder} that
* backs the created builder.
* @return
*/
public static SpanMultiTermQueryBuilder spanMultiTermQueryBuilder(MultiTermQueryBuilder multiTermQueryBuilder){
return new SpanMultiTermQueryBuilder(multiTermQueryBuilder);
}
public static FieldMaskingSpanQueryBuilder fieldMaskingSpanQuery(SpanQueryBuilder query, String field) {
return new FieldMaskingSpanQueryBuilder(query, field);
}

View File

@ -28,7 +28,7 @@ import java.io.IOException;
*
*
*/
public class RangeQueryBuilder extends BaseQueryBuilder implements BoostableQueryBuilder<RangeQueryBuilder> {
public class RangeQueryBuilder extends BaseQueryBuilder implements MultiTermQueryBuilder, BoostableQueryBuilder<RangeQueryBuilder> {
private final String name;

View File

@ -0,0 +1,24 @@
package org.elasticsearch.index.query;
import java.io.IOException;
import org.elasticsearch.common.xcontent.XContentBuilder;
public class SpanMultiTermQueryBuilder extends BaseQueryBuilder implements SpanQueryBuilder{
private MultiTermQueryBuilder multiTermQueryBuilder;
public SpanMultiTermQueryBuilder(MultiTermQueryBuilder multiTermQueryBuilder) {
this.multiTermQueryBuilder = multiTermQueryBuilder;
}
@Override
protected void doXContent(XContentBuilder builder, Params params)
throws IOException {
builder.startObject(SpanMultiTermQueryParser.NAME);
builder.field(SpanMultiTermQueryParser.MATCH_NAME);
multiTermQueryBuilder.toXContent(builder, params);
builder.endObject();
}
}

View File

@ -0,0 +1,82 @@
package org.elasticsearch.index.query;
import java.io.IOException;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token;
public class SpanMultiTermQueryParser implements QueryParser {
public static final String NAME = "span_multi_term";
public static final String MATCH_NAME = "match";
@Inject
public SpanMultiTermQueryParser() {
}
@Override
public String[] names() {
return new String[] { NAME, Strings.toCamelCase(NAME) };
}
@Override
@Nullable
public Query parse(QueryParseContext parseContext) throws IOException,
QueryParsingException {
XContentParser parser = parseContext.parser();
Token token = parser.nextToken();
checkCorrectField(parseContext, parser, token);
token = parser.nextToken();
checkHasObject(parseContext, parser.currentToken());
Query ret = new SpanMultiTermQueryWrapper<MultiTermQuery>(getSubQuery(
parseContext, parser));
parser.nextToken();
return ret;
}
private void checkCorrectField(QueryParseContext parseContext,
XContentParser parser, Token token) throws IOException {
if (!MATCH_NAME.equals(parser.currentName())
|| token != XContentParser.Token.FIELD_NAME) {
throwInvalidClause(parseContext);
}
}
private MultiTermQuery getSubQuery(QueryParseContext parseContext,
XContentParser parser) throws IOException {
return tryParseSubQuery(parseContext, parser);
}
private MultiTermQuery tryParseSubQuery(QueryParseContext parseContext,
XContentParser parser) throws IOException {
Query subQuery = parseContext.parseInnerQuery();
if (!(subQuery instanceof MultiTermQuery)) {
throwInvalidSub(parseContext);
}
return (MultiTermQuery) subQuery;
}
private void throwInvalidSub(QueryParseContext parseContext) {
throw new QueryParsingException(parseContext.index(), "spanMultiTerm ["
+ MATCH_NAME + "] must be of type multi term query");
}
private void checkHasObject(QueryParseContext parseContext, Token token) {
if (token != XContentParser.Token.START_OBJECT) {
throwInvalidClause(parseContext);
}
}
private void throwInvalidClause(QueryParseContext parseContext) {
throw new QueryParsingException(parseContext.index(),
"spanMultiTerm must have [" + MATCH_NAME
+ "] multi term query clause");
}
}

View File

@ -33,7 +33,7 @@ import java.io.IOException;
*
*
*/
public class WildcardQueryBuilder extends BaseQueryBuilder implements BoostableQueryBuilder<WildcardQueryBuilder> {
public class WildcardQueryBuilder extends BaseQueryBuilder implements MultiTermQueryBuilder, BoostableQueryBuilder<WildcardQueryBuilder> {
private final String name;

View File

@ -102,7 +102,7 @@ public class IndicesQueriesModule extends AbstractModule {
qpBinders.addBinding().to(WrapperQueryParser.class).asEagerSingleton();
qpBinders.addBinding().to(IndicesQueryParser.class).asEagerSingleton();
qpBinders.addBinding().to(CommonTermsQueryParser.class).asEagerSingleton();
qpBinders.addBinding().to(SpanMultiTermQueryParser.class).asEagerSingleton();
if (ShapesAvailability.JTS_AVAILABLE) {
qpBinders.addBinding().to(GeoShapeQueryParser.class).asEagerSingleton();
}

View File

@ -1539,6 +1539,60 @@ public class SimpleIndexQueryParserTests {
assertThat(((SpanTermQuery) spanOrQuery.getClauses()[2]).getTerm(), equalTo(new Term("age", longToPrefixCoded(36, 0))));
}
@Test
public void testSpanMultiTermWildcardQuery() throws IOException {
WildcardQuery expectedWrapped = new WildcardQuery(new Term("user", "ki*y"));
expectedWrapped.setBoost(1.08f);
testSpanMultiTerm("/org/elasticsearch/test/unit/index/query/span-multi-term-wildcard.json", expectedWrapped);
}
@Test
public void testSpanMultiTermPrefixQuery() throws IOException {
PrefixQuery expectedWrapped = new PrefixQuery(new Term("user", "ki"));
expectedWrapped.setBoost(1.08f);
testSpanMultiTerm("/org/elasticsearch/test/unit/index/query/span-multi-term-prefix.json", expectedWrapped);
}
@Test
public void testSpanMultiTermFuzzyTermQuery() throws IOException {
IndexQueryParserService queryParser = queryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/test/unit/index/query/span-multi-term-fuzzy-term.json");
Query parsedQuery = queryParser.parse(query).query();
assertThat(parsedQuery, instanceOf(SpanMultiTermQueryWrapper.class));
SpanMultiTermQueryWrapper<MultiTermQuery> wrapper = (SpanMultiTermQueryWrapper<MultiTermQuery>) parsedQuery;
assertThat(wrapper.getField(), equalTo("user"));
}
@Test
public void testSpanMultiTermFuzzyRangeQuery() throws IOException {
NumericRangeQuery<Long> expectedWrapped = NumericRangeQuery.newLongRange("age", 7l, 17l, true, true);
expectedWrapped.setBoost(2.0f);
testSpanMultiTerm("/org/elasticsearch/test/unit/index/query/span-multi-term-fuzzy-range.json", expectedWrapped);
}
@Test
public void testSpanMultiTermNumericRangeQuery() throws IOException {
NumericRangeQuery<Long> expectedWrapped = NumericRangeQuery.newLongRange("age", 10l, 20l, true, false);
expectedWrapped.setBoost(2.0f);
testSpanMultiTerm("/org/elasticsearch/test/unit/index/query/span-multi-term-range-numeric.json", expectedWrapped);
}
@Test
public void testSpanMultiTermTermRangeQuery() throws IOException {
NumericRangeQuery<Long> expectedWrapped = NumericRangeQuery.newLongRange("age", 10l, 20l, true, false);
expectedWrapped.setBoost(2.0f);
testSpanMultiTerm("/org/elasticsearch/test/unit/index/query/span-multi-term-range-numeric.json", expectedWrapped);
}
private void testSpanMultiTerm(String file, MultiTermQuery expectedWrapped) throws IOException{
IndexQueryParserService queryParser = queryParser();
String query = copyToStringFromClasspath(file);
Query parsedQuery = queryParser.parse(query).query();
assertThat(parsedQuery, instanceOf(SpanMultiTermQueryWrapper.class));
SpanMultiTermQueryWrapper<MultiTermQuery> wrapper = (SpanMultiTermQueryWrapper<MultiTermQuery>) parsedQuery;
assertThat(wrapper, equalTo(new SpanMultiTermQueryWrapper<MultiTermQuery>(expectedWrapped)));
}
@Test
public void testQueryFilterBuilder() throws Exception {
IndexQueryParserService queryParser = queryParser();

View File

@ -0,0 +1,13 @@
{
"span_multi_term":{
"match":{
"fuzzy":{
"age":{
"value":12,
"min_similarity":5,
"boost":2.0
}
}
}
}
}

View File

@ -0,0 +1,12 @@
{
"span_multi_term":{
"match":{
"fuzzy" : {
"user" : {
"value" : "ki",
"boost" : 1.08
}
}
}
}
}

View File

@ -0,0 +1,7 @@
{
"span_multi_term":{
"match":{
"prefix" : { "user" : { "value" : "ki", "boost" : 1.08 } }
}
}
}

View File

@ -0,0 +1,16 @@
{
"span_multi_term":{
"match":{
"range" : {
"age" : {
"from" : 10,
"to" : 20,
"include_lower" : true,
"include_upper": false,
"boost" : 2.0
}
}
}
}
}

View File

@ -0,0 +1,16 @@
{
"span_multi_term":{
"match":{
"range" : {
"age" : {
"from" : 10,
"to" : 20,
"include_lower" : true,
"include_upper": false,
"boost" : 2.0
}
}
}
}
}

View File

@ -0,0 +1,7 @@
{
"span_multi_term":{
"match":{
"wildcard" : { "user" : {"value": "ki*y" , "boost" : 1.08}}
}
}
}