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:
parent
ed289dc6c7
commit
e30aa6b221
|
@ -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;
|
private final String name;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.elasticsearch.index.query;
|
||||||
|
|
||||||
|
public interface MultiTermQueryBuilder extends QueryBuilder{
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
private final String name;
|
||||||
|
|
||||||
|
|
|
@ -441,6 +441,20 @@ public abstract class QueryBuilders {
|
||||||
return new SpanOrQueryBuilder();
|
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) {
|
public static FieldMaskingSpanQueryBuilder fieldMaskingSpanQuery(SpanQueryBuilder query, String field) {
|
||||||
return new FieldMaskingSpanQueryBuilder(query, field);
|
return new FieldMaskingSpanQueryBuilder(query, field);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
private final String name;
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
private final String name;
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,7 @@ public class IndicesQueriesModule extends AbstractModule {
|
||||||
qpBinders.addBinding().to(WrapperQueryParser.class).asEagerSingleton();
|
qpBinders.addBinding().to(WrapperQueryParser.class).asEagerSingleton();
|
||||||
qpBinders.addBinding().to(IndicesQueryParser.class).asEagerSingleton();
|
qpBinders.addBinding().to(IndicesQueryParser.class).asEagerSingleton();
|
||||||
qpBinders.addBinding().to(CommonTermsQueryParser.class).asEagerSingleton();
|
qpBinders.addBinding().to(CommonTermsQueryParser.class).asEagerSingleton();
|
||||||
|
qpBinders.addBinding().to(SpanMultiTermQueryParser.class).asEagerSingleton();
|
||||||
if (ShapesAvailability.JTS_AVAILABLE) {
|
if (ShapesAvailability.JTS_AVAILABLE) {
|
||||||
qpBinders.addBinding().to(GeoShapeQueryParser.class).asEagerSingleton();
|
qpBinders.addBinding().to(GeoShapeQueryParser.class).asEagerSingleton();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1539,6 +1539,60 @@ public class SimpleIndexQueryParserTests {
|
||||||
assertThat(((SpanTermQuery) spanOrQuery.getClauses()[2]).getTerm(), equalTo(new Term("age", longToPrefixCoded(36, 0))));
|
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
|
@Test
|
||||||
public void testQueryFilterBuilder() throws Exception {
|
public void testQueryFilterBuilder() throws Exception {
|
||||||
IndexQueryParserService queryParser = queryParser();
|
IndexQueryParserService queryParser = queryParser();
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"span_multi_term":{
|
||||||
|
"match":{
|
||||||
|
"fuzzy":{
|
||||||
|
"age":{
|
||||||
|
"value":12,
|
||||||
|
"min_similarity":5,
|
||||||
|
"boost":2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"span_multi_term":{
|
||||||
|
"match":{
|
||||||
|
"fuzzy" : {
|
||||||
|
"user" : {
|
||||||
|
"value" : "ki",
|
||||||
|
"boost" : 1.08
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"span_multi_term":{
|
||||||
|
"match":{
|
||||||
|
"prefix" : { "user" : { "value" : "ki", "boost" : 1.08 } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"span_multi_term":{
|
||||||
|
"match":{
|
||||||
|
"range" : {
|
||||||
|
"age" : {
|
||||||
|
"from" : 10,
|
||||||
|
"to" : 20,
|
||||||
|
"include_lower" : true,
|
||||||
|
"include_upper": false,
|
||||||
|
"boost" : 2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"span_multi_term":{
|
||||||
|
"match":{
|
||||||
|
"range" : {
|
||||||
|
"age" : {
|
||||||
|
"from" : 10,
|
||||||
|
"to" : 20,
|
||||||
|
"include_lower" : true,
|
||||||
|
"include_upper": false,
|
||||||
|
"boost" : 2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"span_multi_term":{
|
||||||
|
"match":{
|
||||||
|
"wildcard" : { "user" : {"value": "ki*y" , "boost" : 1.08}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue