Query DSL: Field Query, closes #47.

This commit is contained in:
kimchy 2010-03-01 20:10:38 +02:00
parent 12e87ba865
commit 4dbc167966
15 changed files with 428 additions and 11 deletions

View File

@ -0,0 +1,177 @@
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search 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.json;
import org.elasticsearch.util.json.JsonBuilder;
import java.io.IOException;
/**
* @author kimchy (shay.banon)
*/
public class FieldJsonQueryBuilder extends BaseJsonQueryBuilder {
public static enum Operator {
OR,
AND
}
private final String name;
private final Object query;
private Operator defaultOperator;
private String analyzer;
private Boolean allowLeadingWildcard;
private Boolean lowercaseExpandedTerms;
private Boolean enablePositionIncrements;
private float fuzzyMinSim = -1;
private float boost = -1;
private int fuzzyPrefixLength = -1;
private int phraseSlop = -1;
private boolean extraSet = false;
public FieldJsonQueryBuilder(String name, String query) {
this(name, (Object) query);
}
public FieldJsonQueryBuilder(String name, int query) {
this(name, (Object) query);
}
public FieldJsonQueryBuilder(String name, long query) {
this(name, (Object) query);
}
public FieldJsonQueryBuilder(String name, float query) {
this(name, (Object) query);
}
public FieldJsonQueryBuilder(String name, double query) {
this(name, (Object) query);
}
private FieldJsonQueryBuilder(String name, Object query) {
this.name = name;
this.query = query;
}
public FieldJsonQueryBuilder boost(float boost) {
this.boost = boost;
extraSet = true;
return this;
}
public FieldJsonQueryBuilder defaultOperator(Operator defaultOperator) {
this.defaultOperator = defaultOperator;
extraSet = true;
return this;
}
public FieldJsonQueryBuilder analyzer(String analyzer) {
this.analyzer = analyzer;
extraSet = true;
return this;
}
public FieldJsonQueryBuilder allowLeadingWildcard(boolean allowLeadingWildcard) {
this.allowLeadingWildcard = allowLeadingWildcard;
extraSet = true;
return this;
}
public FieldJsonQueryBuilder lowercaseExpandedTerms(boolean lowercaseExpandedTerms) {
this.lowercaseExpandedTerms = lowercaseExpandedTerms;
extraSet = true;
return this;
}
public FieldJsonQueryBuilder enablePositionIncrements(boolean enablePositionIncrements) {
this.enablePositionIncrements = enablePositionIncrements;
extraSet = true;
return this;
}
public FieldJsonQueryBuilder fuzzyMinSim(float fuzzyMinSim) {
this.fuzzyMinSim = fuzzyMinSim;
extraSet = true;
return this;
}
public FieldJsonQueryBuilder fuzzyPrefixLength(int fuzzyPrefixLength) {
this.fuzzyPrefixLength = fuzzyPrefixLength;
extraSet = true;
return this;
}
public FieldJsonQueryBuilder phraseSlop(int phraseSlop) {
this.phraseSlop = phraseSlop;
extraSet = true;
return this;
}
@Override public void doJson(JsonBuilder builder, Params params) throws IOException {
builder.startObject(FieldJsonQueryParser.NAME);
if (!extraSet) {
builder.field(name, query);
} else {
builder.startObject(name);
builder.field("query", query);
if (defaultOperator != null) {
builder.field("defaultOperator", defaultOperator.name().toLowerCase());
}
if (analyzer != null) {
builder.field("analyzer", analyzer);
}
if (allowLeadingWildcard != null) {
builder.field("allowLeadingWildcard", allowLeadingWildcard);
}
if (lowercaseExpandedTerms != null) {
builder.field("lowercaseExpandedTerms", lowercaseExpandedTerms);
}
if (enablePositionIncrements != null) {
builder.field("enablePositionIncrements", enablePositionIncrements);
}
if (fuzzyMinSim != -1) {
builder.field("fuzzyMinSim", fuzzyMinSim);
}
if (boost != -1) {
builder.field("boost", boost);
}
if (fuzzyPrefixLength != -1) {
builder.field("fuzzyPrefixLength", fuzzyPrefixLength);
}
if (phraseSlop != -1) {
builder.field("phraseSlop", phraseSlop);
}
builder.endObject();
}
builder.endObject();
}
}

View File

@ -0,0 +1,167 @@
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search 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.json;
import com.google.inject.Inject;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.Query;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.elasticsearch.index.AbstractIndexComponent;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.analysis.AnalysisService;
import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.index.query.support.MapperQueryParser;
import org.elasticsearch.index.settings.IndexSettings;
import org.elasticsearch.util.settings.Settings;
import java.io.IOException;
import static org.elasticsearch.index.query.support.QueryParsers.*;
/**
* @author kimchy (shay.banon)
*/
public class FieldJsonQueryParser extends AbstractIndexComponent implements JsonQueryParser {
public static final String NAME = "field";
private final AnalysisService analysisService;
@Inject public FieldJsonQueryParser(Index index, @IndexSettings Settings settings, AnalysisService analysisService) {
super(index, settings);
this.analysisService = analysisService;
}
@Override public String name() {
return NAME;
}
@Override public Query parse(JsonQueryParseContext parseContext) throws IOException, QueryParsingException {
JsonParser jp = parseContext.jp();
JsonToken token = jp.nextToken();
assert token == JsonToken.FIELD_NAME;
String fieldName = jp.getCurrentName();
String queryString = null;
float boost = 1.0f;
MapperQueryParser.Operator defaultOperator = QueryParser.Operator.OR;
boolean lowercaseExpandedTerms = true;
boolean enablePositionIncrements = true;
int phraseSlop = 0;
float fuzzyMinSim = FuzzyQuery.defaultMinSimilarity;
int fuzzyPrefixLength = FuzzyQuery.defaultPrefixLength;
boolean escape = false;
Analyzer analyzer = null;
token = jp.nextToken();
if (token == JsonToken.START_OBJECT) {
String currentFieldName = null;
while ((token = jp.nextToken()) != JsonToken.END_OBJECT) {
if (token == JsonToken.FIELD_NAME) {
currentFieldName = jp.getCurrentName();
} else {
if ("query".equals(currentFieldName)) {
queryString = jp.getText();
} else if ("boost".equals(currentFieldName)) {
boost = jp.getFloatValue();
} else if ("enablePositionIncrements".equals(currentFieldName)) {
if (token == JsonToken.VALUE_TRUE) {
enablePositionIncrements = true;
} else if (token == JsonToken.VALUE_FALSE) {
enablePositionIncrements = false;
} else {
enablePositionIncrements = jp.getIntValue() != 0;
}
} else if ("lowercaseExpandedTerms".equals(currentFieldName)) {
if (token == JsonToken.VALUE_TRUE) {
lowercaseExpandedTerms = true;
} else if (token == JsonToken.VALUE_FALSE) {
lowercaseExpandedTerms = false;
} else {
lowercaseExpandedTerms = jp.getIntValue() != 0;
}
} else if ("phraseSlop".equals(currentFieldName)) {
phraseSlop = jp.getIntValue();
} else if ("analyzer".equals(currentFieldName)) {
analyzer = analysisService.analyzer(jp.getText());
} else if ("defaultOperator".equals(currentFieldName)) {
String op = jp.getText();
if ("or".equalsIgnoreCase(op)) {
defaultOperator = QueryParser.Operator.OR;
} else if ("and".equalsIgnoreCase(op)) {
defaultOperator = QueryParser.Operator.AND;
} else {
throw new QueryParsingException(index, "Query default operator [" + op + "] is not allowed");
}
} else if ("fuzzyMinSim".equals(currentFieldName)) {
fuzzyMinSim = jp.getFloatValue();
} else if ("fuzzyPrefixLength".equals(currentFieldName)) {
fuzzyPrefixLength = jp.getIntValue();
} else if ("escape".equals(currentFieldName)) {
if (token == JsonToken.VALUE_TRUE) {
escape = true;
} else if (token == JsonToken.VALUE_FALSE) {
escape = false;
} else {
escape = jp.getIntValue() != 0;
}
}
}
}
jp.nextToken();
} else {
queryString = jp.getText();
// move to the next token
jp.nextToken();
}
if (analyzer == null) {
analyzer = parseContext.mapperService().searchAnalyzer();
}
if (queryString == null) {
throw new QueryParsingException(index, "No value specified for term query");
}
MapperQueryParser queryParser = new MapperQueryParser(fieldName, analyzer, parseContext.mapperService(), parseContext.filterCache());
queryParser.setEnablePositionIncrements(enablePositionIncrements);
queryParser.setLowercaseExpandedTerms(lowercaseExpandedTerms);
queryParser.setPhraseSlop(phraseSlop);
queryParser.setDefaultOperator(defaultOperator);
queryParser.setFuzzyMinSim(fuzzyMinSim);
queryParser.setFuzzyPrefixLength(fuzzyPrefixLength);
if (escape) {
queryString = QueryParser.escape(queryString);
}
try {
Query query = queryParser.parse(queryString);
query.setBoost(boost);
return fixNegativeQueryIfNeeded(query);
} catch (ParseException e) {
throw new QueryParsingException(index, "Failed to parse query [" + queryString + "]", e);
}
}
}

View File

@ -52,8 +52,28 @@ public abstract class JsonQueryBuilders {
return new TermJsonQueryBuilder(name, value);
}
public static PrefixJsonQueryBuilder prefixQuery(String name, String value) {
return new PrefixJsonQueryBuilder(name, value);
public static FieldJsonQueryBuilder fieldQuery(String name, String query) {
return new FieldJsonQueryBuilder(name, query);
}
public static FieldJsonQueryBuilder fieldQuery(String name, int query) {
return new FieldJsonQueryBuilder(name, query);
}
public static FieldJsonQueryBuilder fieldQuery(String name, long query) {
return new FieldJsonQueryBuilder(name, query);
}
public static FieldJsonQueryBuilder fieldQuery(String name, float query) {
return new FieldJsonQueryBuilder(name, query);
}
public static FieldJsonQueryBuilder fieldQuery(String name, double query) {
return new FieldJsonQueryBuilder(name, query);
}
public static PrefixJsonQueryBuilder prefixQuery(String name, String query) {
return new PrefixJsonQueryBuilder(name, query);
}
public static RangeJsonQueryBuilder rangeQuery(String name) {

View File

@ -52,6 +52,7 @@ public class JsonQueryParserRegistry {
add(queryParsersMap, new QueryStringJsonQueryParser(index, indexSettings, analysisService));
add(queryParsersMap, new BoolJsonQueryParser(index, indexSettings));
add(queryParsersMap, new TermJsonQueryParser(index, indexSettings));
add(queryParsersMap, new FieldJsonQueryParser(index, indexSettings, analysisService));
add(queryParsersMap, new RangeJsonQueryParser(index, indexSettings));
add(queryParsersMap, new PrefixJsonQueryParser(index, indexSettings));
add(queryParsersMap, new WildcardJsonQueryParser(index, indexSettings));

View File

@ -64,7 +64,7 @@ public class QueryStringJsonQueryBuilder extends BaseJsonQueryBuilder {
return this;
}
public QueryStringJsonQueryBuilder defualtOperator(Operator defaultOperator) {
public QueryStringJsonQueryBuilder defaultOperator(Operator defaultOperator) {
this.defaultOperator = defaultOperator;
return this;
}

View File

@ -24,7 +24,7 @@ import org.elasticsearch.util.json.JsonBuilder;
import java.io.IOException;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class TermJsonQueryBuilder extends BaseJsonQueryBuilder {

View File

@ -37,7 +37,7 @@ import java.io.IOException;
import static org.elasticsearch.index.query.support.QueryParsers.*;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class TermJsonQueryParser extends AbstractIndexComponent implements JsonQueryParser {

View File

@ -24,7 +24,7 @@ import org.elasticsearch.util.json.JsonBuilder;
import java.io.IOException;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class TermsJsonFilterBuilder extends BaseJsonFilterBuilder {

View File

@ -29,7 +29,7 @@ import org.elasticsearch.util.lucene.search.TermFilter;
import java.util.List;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public final class QueryParsers {

View File

@ -170,9 +170,9 @@ public class RestSearchAction extends BaseRestHandler {
String defaultOperator = request.param("defaultOperator");
if (defaultOperator != null) {
if ("OR".equals(defaultOperator)) {
queryBuilder.defualtOperator(QueryStringJsonQueryBuilder.Operator.OR);
queryBuilder.defaultOperator(QueryStringJsonQueryBuilder.Operator.OR);
} else if ("AND".equals(defaultOperator)) {
queryBuilder.defualtOperator(QueryStringJsonQueryBuilder.Operator.AND);
queryBuilder.defaultOperator(QueryStringJsonQueryBuilder.Operator.AND);
} else {
throw new ElasticSearchIllegalArgumentException("Unsupported defaultOperator [" + defaultOperator + "], can either be [OR] or [AND]");
}

View File

@ -95,9 +95,9 @@ public class RestActions {
String defaultOperator = request.param("defaultOperator");
if (defaultOperator != null) {
if ("OR".equals(defaultOperator)) {
queryBuilder.defualtOperator(QueryStringJsonQueryBuilder.Operator.OR);
queryBuilder.defaultOperator(QueryStringJsonQueryBuilder.Operator.OR);
} else if ("AND".equals(defaultOperator)) {
queryBuilder.defualtOperator(QueryStringJsonQueryBuilder.Operator.AND);
queryBuilder.defaultOperator(QueryStringJsonQueryBuilder.Operator.AND);
} else {
throw new ElasticSearchIllegalArgumentException("Unsupported defaultOperator [" + defaultOperator + "], can either be [OR] or [AND]");
}

View File

@ -150,6 +150,49 @@ public class SimpleJsonIndexQueryParserTests {
assertThat(termQuery.getTerm(), equalTo(new Term("age", NumericUtils.longToPrefixCoded(34))));
}
@Test public void testFieldQueryBuilder1() throws IOException {
IndexQueryParser queryParser = newQueryParser();
Query parsedQuery = queryParser.parse(fieldQuery("age", 34).buildAsBytes());
assertThat(parsedQuery, instanceOf(TermQuery.class));
TermQuery termQuery = (TermQuery) parsedQuery;
// since age is automatically registered in data, we encode it as numeric
assertThat(termQuery.getTerm(), equalTo(new Term("age", NumericUtils.longToPrefixCoded(34))));
}
@Test public void testFieldQuery1() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/json/field1.json");
Query parsedQuery = queryParser.parse(query);
assertThat(parsedQuery, instanceOf(TermQuery.class));
TermQuery termQuery = (TermQuery) parsedQuery;
// since age is automatically registered in data, we encode it as numeric
assertThat(termQuery.getTerm(), equalTo(new Term("age", NumericUtils.longToPrefixCoded(34))));
}
@Test public void testFieldQuery2() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/json/field2.json");
Query parsedQuery = queryParser.parse(query);
assertThat(parsedQuery, instanceOf(BooleanQuery.class));
BooleanQuery bQuery = (BooleanQuery) parsedQuery;
assertThat(bQuery.getClauses().length, equalTo(2));
assertThat(((TermQuery) bQuery.getClauses()[0].getQuery()).getTerm().field(), equalTo("name.first"));
assertThat(((TermQuery) bQuery.getClauses()[0].getQuery()).getTerm().text(), equalTo("12-54-23"));
assertThat(((TermQuery) bQuery.getClauses()[1].getQuery()).getTerm().field(), equalTo("name.first"));
assertThat(((TermQuery) bQuery.getClauses()[1].getQuery()).getTerm().text(), equalTo("else"));
}
@Test public void testFieldQuery3() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/json/field3.json");
Query parsedQuery = queryParser.parse(query);
assertThat((double) parsedQuery.getBoost(), closeTo(2.0, 0.01));
assertThat(parsedQuery, instanceOf(TermQuery.class));
TermQuery termQuery = (TermQuery) parsedQuery;
// since age is automatically registered in data, we encode it as numeric
assertThat(termQuery.getTerm(), equalTo(new Term("age", NumericUtils.longToPrefixCoded(34))));
}
@Test public void testTermWithBoostQueryBuilder() throws IOException {
IndexQueryParser queryParser = newQueryParser();
Query parsedQuery = queryParser.parse(termQuery("age", 34).boost(2.0f));

View File

@ -0,0 +1,3 @@
{
field : { age : 34 }
}

View File

@ -0,0 +1,3 @@
{
field : { "name.first" : "12-54-23 else" }
}

View File

@ -0,0 +1,3 @@
{
field : { age : { query : 34, boost : 2.0, enablePositionIncrements : false } }
}