Search API: Allow to name filters and return per hit the filters it matched on, closes #364.

This commit is contained in:
kimchy 2010-09-11 12:38:19 +03:00
parent ab2a655a59
commit eccc7d5ef2
56 changed files with 1093 additions and 257 deletions

View File

@ -19,7 +19,9 @@
package org.elasticsearch.index.query;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.collect.ImmutableMap;
/**
* The result of parsing a query.
@ -30,8 +32,16 @@ public class ParsedQuery {
private final Query query;
public ParsedQuery(Query query) {
private final ImmutableMap<String, Filter> namedFilters;
public ParsedQuery(Query query, ImmutableMap<String, Filter> namedFilters) {
this.query = query;
this.namedFilters = namedFilters;
}
public ParsedQuery(Query query, ParsedQuery parsedQuery) {
this.query = query;
this.namedFilters = parsedQuery.namedFilters;
}
/**
@ -40,4 +50,8 @@ public class ParsedQuery {
public Query query() {
return this.query;
}
public ImmutableMap<String, Filter> namedFilters() {
return this.namedFilters;
}
}

View File

@ -19,7 +19,11 @@
package org.elasticsearch.index.query.support;
import org.apache.lucene.search.*;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FilteredQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.lucene.search.AndFilter;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.query.xcontent.QueryParseContext;
@ -56,12 +60,6 @@ public final class QueryParsers {
return filter;
}
DocumentMapper docMapper = smartFieldMappers.docMapper();
BooleanFilter booleanFilter = new BooleanFilter();
booleanFilter.add(new FilterClause(parseContext.cacheFilterIfPossible(docMapper.typeFilter()), BooleanClause.Occur.MUST));
booleanFilter.add(new FilterClause(filter, BooleanClause.Occur.MUST));
// don't cache the boolean filter...
return booleanFilter;
return new AndFilter(ImmutableList.of(parseContext.cacheFilterIfPossible(docMapper.typeFilter()), filter));
}
}

View File

@ -36,6 +36,8 @@ public class AndFilterBuilder extends BaseFilterBuilder {
private Boolean cache;
private String filterName;
public AndFilterBuilder(XContentFilterBuilder... filters) {
for (XContentFilterBuilder filter : filters) {
this.filters.add(filter);
@ -58,6 +60,14 @@ public class AndFilterBuilder extends BaseFilterBuilder {
return this;
}
/**
* Sets the filter name for the filter that can be used when searching for matched_filters per hit.
*/
public AndFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(AndFilterParser.NAME);
builder.startArray("filters");
@ -66,7 +76,10 @@ public class AndFilterBuilder extends BaseFilterBuilder {
}
builder.endArray();
if (cache != null) {
builder.field("cache", cache);
builder.field("_cache", cache);
}
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}

View File

@ -56,6 +56,7 @@ public class AndFilterParser extends AbstractIndexComponent implements XContentF
boolean cache = true;
String filterName = null;
String currentFieldName = null;
XContentParser.Token token = parser.currentToken();
if (token == XContentParser.Token.START_ARRAY) {
@ -77,8 +78,10 @@ public class AndFilterParser extends AbstractIndexComponent implements XContentF
}
}
} else if (token.isValue()) {
if ("cache".equals(currentFieldName)) {
if ("_cache".equals(currentFieldName)) {
cache = parser.booleanValue();
} else if ("_name".equals(currentFieldName)) {
filterName = parser.text();
}
}
}
@ -94,6 +97,10 @@ public class AndFilterParser extends AbstractIndexComponent implements XContentF
}
}
// no need to cache this one
return new AndFilter(filters);
AndFilter filter = new AndFilter(filters);
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
}

View File

@ -34,6 +34,8 @@ public class BoolFilterBuilder extends BaseFilterBuilder {
private ArrayList<Clause> clauses = new ArrayList<Clause>();
private String filterName;
/**
* Adds a filter that <b>must</b> appear in the matching documents.
*/
@ -60,6 +62,14 @@ public class BoolFilterBuilder extends BaseFilterBuilder {
return this;
}
/**
* Sets the filter name for the filter that can be used when searching for matched_filters per hit.
*/
public BoolFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("bool");
for (Clause clause : clauses) {
@ -74,6 +84,9 @@ public class BoolFilterBuilder extends BaseFilterBuilder {
clause.filterBuilder.toXContent(builder, params);
}
}
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}

View File

@ -53,6 +53,7 @@ public class BoolFilterParser extends AbstractIndexComponent implements XContent
boolean cache = true;
String filterName = null;
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@ -81,22 +82,27 @@ public class BoolFilterParser extends AbstractIndexComponent implements XContent
}
}
} else if (token.isValue()) {
if ("cache".equals(currentFieldName)) {
if ("_cache".equals(currentFieldName)) {
cache = parser.booleanValue();
} else if ("_name".equals(currentFieldName)) {
filterName = parser.text();
}
}
}
BooleanFilter booleanFilter = new PublicBooleanFilter();
BooleanFilter filter = new PublicBooleanFilter();
for (OpenFilterClause filterClause : clauses) {
if (cache) {
filterClause.setFilter(parseContext.cacheFilterIfPossible(filterClause.getFilter()));
}
booleanFilter.add(filterClause);
filter.add(filterClause);
}
// no need to cache this one, inner queries will be cached and that's is good enough (I think...)
return booleanFilter;
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
}

View File

@ -68,7 +68,7 @@ public class ConstantScoreQueryParser extends AbstractIndexComponent implements
} else if (token.isValue()) {
if ("boost".equals(currentFieldName)) {
boost = parser.floatValue();
} else if ("cache".equals(currentFieldName)) {
} else if ("_cache".equals(currentFieldName)) {
cache = parser.booleanValue();
}
}

View File

@ -0,0 +1,77 @@
/*
* 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.xcontent;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.AbstractIndexComponent;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.index.settings.IndexSettings;
import java.io.IOException;
/**
* @author kimchy (shay.banon)
*/
public class FQueryFilterParser extends AbstractIndexComponent implements XContentFilterParser {
public static final String NAME = "fquery";
@Inject public FQueryFilterParser(Index index, @IndexSettings Settings settings) {
super(index, settings);
}
@Override public String[] names() {
return new String[]{NAME};
}
@Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
Query query = null;
String filterName = 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 ("query".equals(currentFieldName)) {
query = parseContext.parseInnerQuery();
}
} else if (token.isValue()) {
if ("_name".equals(currentFieldName)) {
filterName = parser.text();
}
}
}
Filter filter = new QueryWrapperFilter(query);
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
}

View File

@ -40,6 +40,8 @@ public class GeoBoundingBoxFilterBuilder extends BaseFilterBuilder {
private String bottomRightGeohash;
private String filterName;
public GeoBoundingBoxFilterBuilder(String name) {
this.name = name;
}
@ -68,6 +70,14 @@ public class GeoBoundingBoxFilterBuilder extends BaseFilterBuilder {
return this;
}
/**
* Sets the filter name for the filter that can be used when searching for matched_filters per hit.
*/
public GeoBoundingBoxFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(GeoBoundingBoxFilterParser.NAME);
@ -89,6 +99,10 @@ public class GeoBoundingBoxFilterBuilder extends BaseFilterBuilder {
}
builder.endObject();
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}
}

View File

@ -53,20 +53,21 @@ public class GeoBoundingBoxFilterParser extends AbstractIndexComponent implement
@Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
XContentParser.Token token = parser.nextToken();
assert token == XContentParser.Token.FIELD_NAME;
String latFieldName = parser.currentName() + XContentGeoPointFieldMapper.Names.LAT_SUFFIX;
String lonFieldName = parser.currentName() + XContentGeoPointFieldMapper.Names.LON_SUFFIX;
// now, we move after the field name, which starts the object
token = parser.nextToken();
assert token == XContentParser.Token.START_OBJECT;
String latFieldName = null;
String lonFieldName = null;
GeoBoundingBoxFilter.Point topLeft = new GeoBoundingBoxFilter.Point();
GeoBoundingBoxFilter.Point bottomRight = new GeoBoundingBoxFilter.Point();
String filterName = 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) {
latFieldName = currentFieldName + XContentGeoPointFieldMapper.Names.LAT_SUFFIX;
lonFieldName = currentFieldName + XContentGeoPointFieldMapper.Names.LON_SUFFIX;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
@ -139,6 +140,12 @@ public class GeoBoundingBoxFilterParser extends AbstractIndexComponent implement
}
}
}
} else if (token.isValue()) {
if ("_name".equals(currentFieldName)) {
filterName = parser.text();
}
}
}
MapperService mapperService = parseContext.mapperService();
FieldMapper mapper = mapperService.smartNameFieldMapper(latFieldName);
@ -154,6 +161,10 @@ public class GeoBoundingBoxFilterParser extends AbstractIndexComponent implement
lonFieldName = mapper.names().indexName();
return new GeoBoundingBoxFilter(topLeft, bottomRight, latFieldName, lonFieldName, mapper.fieldDataType(), parseContext.indexCache().fieldData());
GeoBoundingBoxFilter filter = new GeoBoundingBoxFilter(topLeft, bottomRight, latFieldName, lonFieldName, mapper.fieldDataType(), parseContext.indexCache().fieldData());
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
}

View File

@ -42,6 +42,8 @@ public class GeoDistanceFilterBuilder extends BaseFilterBuilder {
private GeoDistance geoDistance;
private String filterName;
public GeoDistanceFilterBuilder(String name) {
this.name = name;
}
@ -82,6 +84,11 @@ public class GeoDistanceFilterBuilder extends BaseFilterBuilder {
return this;
}
public GeoDistanceFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(GeoDistanceFilterParser.NAME);
if (geohash != null) {
@ -93,6 +100,9 @@ public class GeoDistanceFilterBuilder extends BaseFilterBuilder {
if (geoDistance != null) {
builder.field("distance_type", geoDistance.name().toLowerCase());
}
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}
}

View File

@ -64,6 +64,7 @@ public class GeoDistanceFilterParser extends AbstractIndexComponent implements X
XContentParser.Token token;
String filterName = null;
String currentFieldName = null;
double lat = 0;
double lon = 0;
@ -128,6 +129,8 @@ public class GeoDistanceFilterParser extends AbstractIndexComponent implements X
lon = values[1];
latFieldName = currentFieldName.substring(0, currentFieldName.length() - XContentGeoPointFieldMapper.Names.GEOHASH_SUFFIX.length()) + XContentGeoPointFieldMapper.Names.LAT_SUFFIX;
lonFieldName = currentFieldName.substring(0, currentFieldName.length() - XContentGeoPointFieldMapper.Names.GEOHASH_SUFFIX.length()) + XContentGeoPointFieldMapper.Names.LON_SUFFIX;
} else if ("_name".equals(currentFieldName)) {
filterName = parser.text();
} else {
// assume the value is the actual value
String value = parser.text();
@ -164,6 +167,10 @@ public class GeoDistanceFilterParser extends AbstractIndexComponent implements X
}
lonFieldName = mapper.names().indexName();
return new GeoDistanceFilter(lat, lon, distance, geoDistance, latFieldName, lonFieldName, mapper.fieldDataType(), parseContext.indexCache().fieldData());
GeoDistanceFilter filter = new GeoDistanceFilter(lat, lon, distance, geoDistance, latFieldName, lonFieldName, mapper.fieldDataType(), parseContext.indexCache().fieldData());
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
}

View File

@ -36,6 +36,8 @@ public class GeoPolygonFilterBuilder extends BaseFilterBuilder {
private final List<GeoPolygonFilter.Point> points = Lists.newArrayList();
private String filterName;
public GeoPolygonFilterBuilder(String name) {
this.name = name;
}
@ -50,6 +52,11 @@ public class GeoPolygonFilterBuilder extends BaseFilterBuilder {
return addPoint(values[0], values[1]);
}
public GeoPolygonFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(GeoPolygonFilterParser.NAME);
@ -61,6 +68,10 @@ public class GeoPolygonFilterBuilder extends BaseFilterBuilder {
builder.endArray();
builder.endObject();
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}
}

View File

@ -66,19 +66,22 @@ public class GeoPolygonFilterParser extends AbstractIndexComponent implements XC
@Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
XContentParser.Token token = parser.nextToken();
assert token == XContentParser.Token.FIELD_NAME;
String latFieldName = parser.currentName() + XContentGeoPointFieldMapper.Names.LAT_SUFFIX;
String lonFieldName = parser.currentName() + XContentGeoPointFieldMapper.Names.LON_SUFFIX;
// now, we move after the field name, which starts the object
token = parser.nextToken();
assert token == XContentParser.Token.START_OBJECT;
String latFieldName = null;
String lonFieldName = null;
List<GeoPolygonFilter.Point> points = Lists.newArrayList();
String filterName = 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) {
latFieldName = currentFieldName + XContentGeoPointFieldMapper.Names.LAT_SUFFIX;
lonFieldName = currentFieldName + XContentGeoPointFieldMapper.Names.LON_SUFFIX;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
@ -133,6 +136,12 @@ public class GeoPolygonFilterParser extends AbstractIndexComponent implements XC
}
}
}
} else if (token.isValue()) {
if ("_name".equals(currentFieldName)) {
filterName = parser.text();
}
}
}
if (points.isEmpty()) {
throw new QueryParsingException(index, "no points defined for geo_polygon filter");
@ -151,6 +160,10 @@ public class GeoPolygonFilterParser extends AbstractIndexComponent implements XC
}
lonFieldName = mapper.names().indexName();
return new GeoPolygonFilter(points.toArray(new GeoPolygonFilter.Point[points.size()]), latFieldName, lonFieldName, mapper.fieldDataType(), parseContext.indexCache().fieldData());
GeoPolygonFilter filter = new GeoPolygonFilter(points.toArray(new GeoPolygonFilter.Point[points.size()]), latFieldName, lonFieldName, mapper.fieldDataType(), parseContext.indexCache().fieldData());
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
}

View File

@ -34,6 +34,8 @@ public class NotFilterBuilder extends BaseFilterBuilder {
private Boolean cache;
private String filterName;
public NotFilterBuilder(XContentFilterBuilder filter) {
this.filter = filter;
}
@ -46,12 +48,20 @@ public class NotFilterBuilder extends BaseFilterBuilder {
return this;
}
public NotFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NotFilterParser.NAME);
builder.field("filter");
filter.toXContent(builder, params);
if (cache != null) {
builder.field("cache", cache);
builder.field("_cache", cache);
}
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}

View File

@ -52,6 +52,7 @@ public class NotFilterParser extends AbstractIndexComponent implements XContentF
Filter filter = null;
boolean cache = true;
String filterName = null;
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@ -62,8 +63,10 @@ public class NotFilterParser extends AbstractIndexComponent implements XContentF
filter = parseContext.parseInnerFilter();
}
} else if (token.isValue()) {
if ("cache".equals(currentFieldName)) {
if ("_cache".equals(currentFieldName)) {
cache = parser.booleanValue();
} else if ("_name".equals(currentFieldName)) {
filterName = parser.text();
}
}
}
@ -76,6 +79,10 @@ public class NotFilterParser extends AbstractIndexComponent implements XContentF
filter = parseContext.cacheFilterIfPossible(filter);
}
// no need to cache this one
return new NotFilter(filter);
NotFilter notFilter = new NotFilter(filter);
if (filterName != null) {
parseContext.addNamedFilter(filterName, notFilter);
}
return notFilter;
}
}

View File

@ -36,6 +36,8 @@ public class OrFilterBuilder extends BaseFilterBuilder {
private Boolean cache;
private String filterName;
public OrFilterBuilder(XContentFilterBuilder... filters) {
for (XContentFilterBuilder filter : filters) {
this.filters.add(filter);
@ -58,6 +60,11 @@ public class OrFilterBuilder extends BaseFilterBuilder {
return this;
}
public OrFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(OrFilterParser.NAME);
builder.startArray("filters");
@ -66,7 +73,10 @@ public class OrFilterBuilder extends BaseFilterBuilder {
}
builder.endArray();
if (cache != null) {
builder.field("cache", cache);
builder.field("_cache", cache);
}
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}

View File

@ -56,6 +56,7 @@ public class OrFilterParser extends AbstractIndexComponent implements XContentFi
boolean cache = true;
String filterName = null;
String currentFieldName = null;
XContentParser.Token token = parser.currentToken();
if (token == XContentParser.Token.START_ARRAY) {
@ -77,8 +78,10 @@ public class OrFilterParser extends AbstractIndexComponent implements XContentFi
}
}
} else if (token.isValue()) {
if ("cache".equals(currentFieldName)) {
if ("_cache".equals(currentFieldName)) {
cache = parser.booleanValue();
} else if ("_name".equals(currentFieldName)) {
filterName = parser.text();
}
}
}
@ -94,6 +97,10 @@ public class OrFilterParser extends AbstractIndexComponent implements XContentFi
}
}
// no need to cache this one
return new OrFilter(filters);
OrFilter filter = new OrFilter(filters);
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
}

View File

@ -35,6 +35,8 @@ public class PrefixFilterBuilder extends BaseFilterBuilder {
private final String prefix;
private String filterName;
/**
* A filter that restricts search results to values that have a matching prefix in a given
* field.
@ -47,9 +49,17 @@ public class PrefixFilterBuilder extends BaseFilterBuilder {
this.prefix = prefix;
}
public PrefixFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override public void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(PrefixFilterParser.NAME);
builder.field(name, prefix);
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}
}

View File

@ -53,15 +53,27 @@ public class PrefixFilterParser extends AbstractIndexComponent implements XConte
@Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
XContentParser.Token token = parser.nextToken();
assert token == XContentParser.Token.FIELD_NAME;
String fieldName = parser.currentName();
parser.nextToken();
String value = parser.text();
parser.nextToken();
String fieldName = null;
String value = null;
String filterName = 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.isValue()) {
if ("_name".equals(currentFieldName)) {
filterName = parser.text();
} else {
fieldName = currentFieldName;
value = parser.text();
}
}
}
if (value == null) {
throw new QueryParsingException(index, "No value specified for prefix query");
throw new QueryParsingException(index, "No value specified for prefix filter");
}
MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName);
@ -72,7 +84,11 @@ public class PrefixFilterParser extends AbstractIndexComponent implements XConte
}
}
Filter prefixFilter = new PrefixFilter(new Term(fieldName, value));
return wrapSmartNameFilter(prefixFilter, smartNameFieldMappers, parseContext);
Filter filter = new PrefixFilter(new Term(fieldName, value));
filter = wrapSmartNameFilter(filter, smartNameFieldMappers, parseContext);
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
}

View File

@ -32,6 +32,8 @@ public class QueryFilterBuilder extends BaseFilterBuilder {
private final XContentQueryBuilder queryBuilder;
private String filterName;
/**
* A filter that simply wraps a query.
*
@ -41,8 +43,21 @@ public class QueryFilterBuilder extends BaseFilterBuilder {
this.queryBuilder = queryBuilder;
}
public QueryFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
if (filterName != null) {
builder.field(QueryFilterParser.NAME);
queryBuilder.toXContent(builder, params);
} else {
builder.startObject(FQueryFilterParser.NAME);
builder.field("query");
queryBuilder.toXContent(builder, params);
builder.field("_name", filterName);
builder.endObject();
}
}
}

View File

@ -24,7 +24,6 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.AbstractIndexComponent;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.query.QueryParsingException;
@ -48,10 +47,7 @@ public class QueryFilterParser extends AbstractIndexComponent implements XConten
}
@Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
Query query = parseContext.parseInnerQuery();
Filter filter = new QueryWrapperFilter(query);
return filter;
return new QueryWrapperFilter(query);
}
}

View File

@ -22,6 +22,8 @@ package org.elasticsearch.index.query.xcontent;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Similarity;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.collect.Maps;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.cache.IndexCache;
@ -35,6 +37,7 @@ import org.elasticsearch.script.ScriptService;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Map;
/**
* @author kimchy (shay.banon)
@ -55,6 +58,8 @@ public class QueryParseContext {
private final XContentQueryParserRegistry queryParserRegistry;
private final Map<String, Filter> namedFilters = Maps.newHashMap();
private XContentParser parser;
public QueryParseContext(Index index, XContentQueryParserRegistry queryParserRegistry,
@ -72,6 +77,7 @@ public class QueryParseContext {
public void reset(XContentParser jp) {
this.parser = jp;
this.namedFilters.clear();
}
public XContentParser parser() {
@ -106,6 +112,17 @@ public class QueryParseContext {
return indexCache.filter().cache(filter);
}
public void addNamedFilter(String name, Filter filter) {
namedFilters.put(name, filter);
}
public ImmutableMap<String, Filter> copyNamedFilters() {
if (namedFilters.isEmpty()) {
return ImmutableMap.of();
}
return ImmutableMap.copyOf(namedFilters);
}
public Query parseInnerQuery() throws IOException, QueryParsingException {
// move to START object
XContentParser.Token token;

View File

@ -40,6 +40,8 @@ public class RangeFilterBuilder extends BaseFilterBuilder {
private boolean includeUpper = true;
private String filterName;
/**
* A filter that restricts search results to values that are within the given range.
*
@ -325,14 +327,25 @@ public class RangeFilterBuilder extends BaseFilterBuilder {
return this;
}
public RangeFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(RangeFilterParser.NAME);
builder.startObject(name);
builder.field("from", from);
builder.field("to", to);
builder.field("include_lower", includeLower);
builder.field("include_upper", includeUpper);
builder.endObject();
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}
}

View File

@ -52,20 +52,20 @@ public class RangeFilterParser extends AbstractIndexComponent implements XConten
@Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
XContentParser.Token token = parser.nextToken();
assert token == XContentParser.Token.FIELD_NAME;
String fieldName = parser.currentName();
// now, we move after the field name, which starts the object
token = parser.nextToken();
assert token == XContentParser.Token.START_OBJECT;
String fieldName = null;
String from = null;
String to = null;
boolean includeLower = true;
boolean includeUpper = true;
String filterName = 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) {
fieldName = currentFieldName;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
@ -93,10 +93,12 @@ public class RangeFilterParser extends AbstractIndexComponent implements XConten
}
}
}
// move to the next end object, to close the field name
token = parser.nextToken();
assert token == XContentParser.Token.END_OBJECT;
} else if (token.isValue()) {
if ("_name".equals(currentFieldName)) {
filterName = parser.text();
}
}
}
Filter filter = null;
MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName);
@ -108,6 +110,10 @@ public class RangeFilterParser extends AbstractIndexComponent implements XConten
if (filter == null) {
filter = new TermRangeFilter(fieldName, from, to, includeLower, includeUpper);
}
return wrapSmartNameFilter(filter, smartNameFieldMappers, parseContext);
filter = wrapSmartNameFilter(filter, smartNameFieldMappers, parseContext);
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
}

View File

@ -35,6 +35,8 @@ public class ScriptFilterBuilder extends BaseFilterBuilder {
private Map<String, Object> params;
private String filterName;
public ScriptFilterBuilder(String script) {
this.script = script;
}
@ -56,12 +58,20 @@ public class ScriptFilterBuilder extends BaseFilterBuilder {
return this;
}
public ScriptFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(ScriptFilterParser.NAME);
builder.field("script", script);
if (this.params != null) {
builder.field("params", this.params);
}
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}
}

View File

@ -62,6 +62,7 @@ public class ScriptFilterParser extends AbstractIndexComponent implements XConte
String script = null;
Map<String, Object> params = null;
String filterName = null;
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
@ -73,6 +74,8 @@ public class ScriptFilterParser extends AbstractIndexComponent implements XConte
} else if (token.isValue()) {
if ("script".equals(currentFieldName)) {
script = parser.text();
} else if ("_name".equals(currentFieldName)) {
filterName = parser.text();
}
}
}
@ -84,7 +87,11 @@ public class ScriptFilterParser extends AbstractIndexComponent implements XConte
params = Maps.newHashMap();
}
return new ScriptFilter(script, params, parseContext.mapperService(), parseContext.indexCache().fieldData(), parseContext.scriptService());
Filter filter = new ScriptFilter(script, params, parseContext.mapperService(), parseContext.indexCache().fieldData(), parseContext.scriptService());
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
public static class ScriptFilter extends Filter {

View File

@ -34,6 +34,8 @@ public class TermFilterBuilder extends BaseFilterBuilder {
private final Object value;
private String filterName;
/**
* A filter for a field based on a term.
*
@ -95,9 +97,17 @@ public class TermFilterBuilder extends BaseFilterBuilder {
this.value = value;
}
public TermFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override public void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(TermFilterParser.NAME);
builder.field(name, value);
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}
}

View File

@ -53,15 +53,24 @@ public class TermFilterParser extends AbstractIndexComponent implements XContent
@Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
XContentParser.Token token = parser.nextToken();
assert token == XContentParser.Token.FIELD_NAME;
String fieldName = parser.currentName();
String fieldName = null;
String value = null;
parser.nextToken();
String value = parser.text();
// move to the next token (from VALUE)
parser.nextToken();
String filterName = 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.isValue()) {
if ("_name".equals(currentFieldName)) {
filterName = parser.text();
} else {
fieldName = currentFieldName;
value = parser.text();
}
}
}
if (value == null) {
throw new QueryParsingException(index, "No value specified for term filter");
@ -77,6 +86,10 @@ public class TermFilterParser extends AbstractIndexComponent implements XContent
if (filter == null) {
filter = new TermFilter(new Term(fieldName, value));
}
return wrapSmartNameFilter(filter, smartNameFieldMappers, parseContext);
filter = wrapSmartNameFilter(filter, smartNameFieldMappers, parseContext);
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
}

View File

@ -34,6 +34,8 @@ public class TermsFilterBuilder extends BaseFilterBuilder {
private final Object[] values;
private String filterName;
/**
* A filer for a field based on several terms matching on any of them.
*
@ -111,6 +113,11 @@ public class TermsFilterBuilder extends BaseFilterBuilder {
this.values = values;
}
public TermsFilterBuilder filterName(String filterName) {
this.filterName = filterName;
return this;
}
@Override public void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(TermsFilterParser.NAME);
builder.startArray(name);
@ -118,6 +125,11 @@ public class TermsFilterBuilder extends BaseFilterBuilder {
builder.value(value);
}
builder.endArray();
if (filterName != null) {
builder.field("_name", filterName);
}
builder.endObject();
}
}

View File

@ -55,12 +55,18 @@ public class TermsFilterParser extends AbstractIndexComponent implements XConten
@Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
XContentParser.Token token = parser.nextToken();
assert token == XContentParser.Token.FIELD_NAME;
String fieldName = parser.currentName();
MapperService.SmartNameFieldMappers smartNameFieldMappers = null;
TermsFilter termsFilter = new PublicTermsFilter();
String filterName = 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_ARRAY) {
String fieldName = currentFieldName;
FieldMapper fieldMapper = null;
MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName);
smartNameFieldMappers = parseContext.smartFieldMappers(fieldName);
if (smartNameFieldMappers != null) {
if (smartNameFieldMappers.hasMapper()) {
fieldMapper = smartNameFieldMappers.mapper();
@ -68,12 +74,6 @@ public class TermsFilterParser extends AbstractIndexComponent implements XConten
}
}
token = parser.nextToken();
if (token != XContentParser.Token.START_ARRAY) {
throw new QueryParsingException(index, "Terms filter must define the terms to filter on as an array");
}
TermsFilter termsFilter = new PublicTermsFilter();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
String value = parser.text();
if (value == null) {
@ -84,9 +84,17 @@ public class TermsFilterParser extends AbstractIndexComponent implements XConten
}
termsFilter.addTerm(new Term(fieldName, value));
}
parser.nextToken();
return wrapSmartNameFilter(termsFilter, smartNameFieldMappers, parseContext);
} else if (token.isValue()) {
if ("_name".equals(currentFieldName)) {
filterName = parser.text();
}
}
}
Filter filter = wrapSmartNameFilter(termsFilter, smartNameFieldMappers, parseContext);
if (filterName != null) {
parseContext.addNamedFilter(filterName, filter);
}
return filter;
}
}

View File

@ -210,6 +210,7 @@ public class XContentIndexQueryParser extends AbstractIndexComponent implements
private ParsedQuery parse(QueryParseContext parseContext, XContentParser parser) throws IOException, QueryParsingException {
parseContext.reset(parser);
return new ParsedQuery(parseContext.parseInnerQuery());
Query query = parseContext.parseInnerQuery();
return new ParsedQuery(query, parseContext.copyNamedFilters());
}
}

View File

@ -90,6 +90,7 @@ public class XContentQueryParserRegistry {
add(filterParsersMap, new GeoBoundingBoxFilterParser(index, indexSettings));
add(filterParsersMap, new GeoPolygonFilterParser(index, indexSettings));
add(filterParsersMap, new QueryFilterParser(index, indexSettings));
add(filterParsersMap, new FQueryFilterParser(index, indexSettings));
add(filterParsersMap, new BoolFilterParser(index, indexSettings));
add(filterParsersMap, new AndFilterParser(index, indexSettings));
add(filterParsersMap, new OrFilterParser(index, indexSettings));

View File

@ -37,7 +37,7 @@ public class SearchContextException extends SearchException {
private static String buildMessage(SearchContext context, String msg) {
StringBuilder sb = new StringBuilder();
sb.append('[').append(context.shardTarget().index()).append("][").append(context.shardTarget().shardId()).append("]: ");
sb.append("query[").append(context.originalQuery()).append("],from[").append(context.from()).append("],size[").append(context.size()).append("]");
sb.append("query[").append(context.parsedQuery().query()).append("],from[").append(context.from()).append("],size[").append(context.size()).append("]");
if (context.sort() != null) {
sb.append(",sort[").append(context.sort()).append("]");
}

View File

@ -147,6 +147,16 @@ public interface SearchHit extends Streamable, ToXContent, Iterable<SearchHitFie
*/
Object[] getSortValues();
/**
* The set of filter names the query matched. Mainly makes sense for OR filters.
*/
String[] matchedFilters();
/**
* The set of filter names the query matched. Mainly makes sense for OR filters.
*/
String[] getMatchedFilters();
/**
* The shard of the search hit.
*/

View File

@ -23,8 +23,12 @@ import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.Filter;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.docset.DocSet;
import org.elasticsearch.common.thread.ThreadLocals;
import org.elasticsearch.index.mapper.*;
import org.elasticsearch.search.SearchHitField;
@ -41,6 +45,7 @@ import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -159,6 +164,27 @@ public class FetchPhase implements SearchPhase {
sameDocCache.clear();
}
if (!context.parsedQuery().namedFilters().isEmpty()) {
int readerIndex = context.searcher().readerIndex(docId);
IndexReader subReader = context.searcher().subReaders()[readerIndex];
int subDoc = docId - context.searcher().docStarts()[readerIndex];
List<String> matchedFilters = Lists.newArrayListWithCapacity(2);
for (Map.Entry<String, Filter> entry : context.parsedQuery().namedFilters().entrySet()) {
String name = entry.getKey();
Filter filter = entry.getValue();
filter = context.filterCache().cache(filter);
try {
DocIdSet docIdSet = filter.getDocIdSet(subReader);
if (docIdSet instanceof DocSet && ((DocSet) docIdSet).get(subDoc)) {
matchedFilters.add(name);
}
} catch (IOException e) {
// ignore
}
}
searchHit.matchedFilters(matchedFilters.toArray(new String[matchedFilters.size()]));
}
doExplanation(context, docId, searchHit);
}
context.fetchResult().hits(new InternalSearchHits(hits, context.queryResult().topDocs().totalHits, context.queryResult().topDocs().getMaxScore()));

View File

@ -80,7 +80,7 @@ public class ContextIndexSearcher extends ExtendedIndexSearcher {
}
@Override public Query rewrite(Query original) throws IOException {
if (original == searchContext.query() || original == searchContext.originalQuery()) {
if (original == searchContext.query() || original == searchContext.parsedQuery().query()) {
// optimize in case its the top level search query and we already rewrote it...
if (searchContext.queryRewritten()) {
return searchContext.query();

View File

@ -21,6 +21,7 @@ package org.elasticsearch.search.internal;
import org.apache.lucene.search.Explanation;
import org.elasticsearch.ElasticSearchParseException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.Unicode;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.compress.lzf.LZFDecoder;
@ -69,6 +70,8 @@ public class InternalSearchHit implements SearchHit {
private Object[] sortValues = EMPTY_SORT_VALUES;
private String[] matchedFilters = Strings.EMPTY_ARRAY;
private Explanation explanation;
@Nullable private SearchShardTarget shard;
@ -248,6 +251,18 @@ public class InternalSearchHit implements SearchHit {
this.shard = target;
}
public void matchedFilters(String[] matchedFilters) {
this.matchedFilters = matchedFilters;
}
public String[] matchedFilters() {
return this.matchedFilters;
}
@Override public String[] getMatchedFilters() {
return this.matchedFilters;
}
@Override public void toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("_index", shard.index());
@ -305,6 +320,13 @@ public class InternalSearchHit implements SearchHit {
}
builder.endArray();
}
if (matchedFilters.length > 0) {
builder.startArray("matched_filters");
for (String matchedFilter : matchedFilters) {
builder.value(matchedFilter);
}
builder.endArray();
}
if (explanation() != null) {
builder.field("_explanation");
buildExplanation(builder, explanation());
@ -447,6 +469,14 @@ public class InternalSearchHit implements SearchHit {
}
}
size = in.readVInt();
if (size > 0) {
matchedFilters = new String[size];
for (int i = 0; i < size; i++) {
matchedFilters[i] = in.readUTF();
}
}
if (shardLookupMap != null) {
int lookupId = in.readVInt();
if (lookupId > 0) {
@ -530,6 +560,15 @@ public class InternalSearchHit implements SearchHit {
}
}
if (matchedFilters.length == 0) {
out.writeVInt(0);
} else {
out.writeVInt(matchedFilters.length);
for (String matchedFilter : matchedFilters) {
out.writeUTF(matchedFilter);
}
}
if (shardLookupMap == null) {
if (shard == null) {
out.writeBoolean(false);

View File

@ -34,6 +34,7 @@ import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.query.IndexQueryParser;
import org.elasticsearch.index.query.IndexQueryParserMissingException;
import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.index.query.ParsedQuery;
import org.elasticsearch.index.service.IndexService;
import org.elasticsearch.index.similarity.SimilarityService;
import org.elasticsearch.script.ScriptService;
@ -94,7 +95,7 @@ public class SearchContext implements Releasable {
private String queryParserName;
private Query originalQuery;
private ParsedQuery originalQuery;
private Query query;
@ -272,17 +273,14 @@ public class SearchContext implements Releasable {
return this;
}
public SearchContext query(Query query) {
public SearchContext parsedQuery(ParsedQuery query) {
queryRewritten = false;
this.originalQuery = query;
this.query = query;
this.query = query.query();
return this;
}
/**
* The original query to execute, unmodified.
*/
public Query originalQuery() {
public ParsedQuery parsedQuery() {
return this.originalQuery;
}

View File

@ -19,7 +19,6 @@
package org.elasticsearch.search.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.xcontent.XContentIndexQueryParser;
@ -35,7 +34,6 @@ public class QueryBinaryParseElement implements SearchParseElement {
XContentIndexQueryParser indexQueryParser = (XContentIndexQueryParser) context.queryParser();
byte[] querySource = parser.binaryValue();
XContentParser qSourceParser = XContentFactory.xContent(querySource).createParser(querySource);
Query query = indexQueryParser.parse(qSourceParser).query();
context.query(query);
context.parsedQuery(indexQueryParser.parse(qSourceParser));
}
}

View File

@ -19,7 +19,6 @@
package org.elasticsearch.search.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.xcontent.XContentIndexQueryParser;
import org.elasticsearch.search.SearchParseElement;
@ -32,7 +31,6 @@ public class QueryParseElement implements SearchParseElement {
@Override public void parse(XContentParser parser, SearchContext context) throws Exception {
XContentIndexQueryParser indexQueryParser = (XContentIndexQueryParser) context.queryParser();
Query query = indexQueryParser.parse(parser).query();
context.query(query);
context.parsedQuery(indexQueryParser.parse(parser));
}
}

View File

@ -26,6 +26,7 @@ import org.elasticsearch.common.lucene.search.function.BoostScoreFunction;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.query.ParsedQuery;
import org.elasticsearch.indices.TypeMissingException;
import org.elasticsearch.search.SearchParseElement;
import org.elasticsearch.search.SearchParseException;
@ -67,7 +68,7 @@ public class QueryPhase implements SearchPhase {
throw new SearchParseException(context, "No query specified in search request");
}
if (context.queryBoost() != 1.0f) {
context.query(new FunctionScoreQuery(context.query(), new BoostScoreFunction(context.queryBoost())));
context.parsedQuery(new ParsedQuery(new FunctionScoreQuery(context.query(), new BoostScoreFunction(context.queryBoost())), context.parsedQuery()));
}
facetsPhase.preProcess(context);
}

View File

@ -36,6 +36,7 @@ import org.elasticsearch.index.cache.IndexCache;
import org.elasticsearch.index.engine.robin.RobinIndexEngine;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.query.IndexQueryParser;
import org.elasticsearch.index.query.ParsedQuery;
import org.elasticsearch.script.ScriptService;
import org.testng.annotations.Test;
@ -375,6 +376,17 @@ public class SimpleIndexQueryParserTests {
assertThat(prefixFilter.getPrefix(), equalTo(new Term("name.first", "sh")));
}
@Test public void testPrefixNamedFilteredQuery() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/prefix-filter-named.json");
ParsedQuery parsedQuery = queryParser.parse(query);
assertThat(parsedQuery.namedFilters().containsKey("test"), equalTo(true));
assertThat(parsedQuery.query(), instanceOf(FilteredQuery.class));
FilteredQuery filteredQuery = (FilteredQuery) parsedQuery.query();
PrefixFilter prefixFilter = (PrefixFilter) filteredQuery.getFilter();
assertThat(prefixFilter.getPrefix(), equalTo(new Term("name.first", "sh")));
}
@Test public void testPrefixQueryBoostQueryBuilder() throws IOException {
IndexQueryParser queryParser = newQueryParser();
Query parsedQuery = queryParser.parse(prefixQuery("name.first", "sh").boost(2.0f)).query();
@ -483,6 +495,22 @@ public class SimpleIndexQueryParserTests {
assertThat(rangeFilter.includesMax(), equalTo(false));
}
@Test public void testRangeNamedFilteredQuery() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/range-filter-named.json");
ParsedQuery parsedQuery = queryParser.parse(query);
assertThat(parsedQuery.namedFilters().containsKey("test"), equalTo(true));
assertThat(parsedQuery.query(), instanceOf(FilteredQuery.class));
Filter filter = ((FilteredQuery) parsedQuery.query()).getFilter();
assertThat(filter, instanceOf(NumericRangeFilter.class));
NumericRangeFilter rangeFilter = (NumericRangeFilter) filter;
assertThat(rangeFilter.getField(), equalTo("age"));
assertThat(rangeFilter.getMin().intValue(), equalTo(23));
assertThat(rangeFilter.getMax().intValue(), equalTo(54));
assertThat(rangeFilter.includesMin(), equalTo(true));
assertThat(rangeFilter.includesMax(), equalTo(false));
}
@Test public void testBoolFilteredQuery() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/bool-filter.json");
@ -519,6 +547,20 @@ public class SimpleIndexQueryParserTests {
assertThat(((TermFilter) andFilter.filters().get(1)).getTerm(), equalTo(new Term("name.first", "shay4")));
}
@Test public void testAndNamedFilteredQuery() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/and-filter-named.json");
ParsedQuery parsedQuery = queryParser.parse(query);
assertThat(parsedQuery.namedFilters().containsKey("test"), equalTo(true));
assertThat(parsedQuery.query(), instanceOf(FilteredQuery.class));
FilteredQuery filteredQuery = (FilteredQuery) parsedQuery.query();
AndFilter andFilter = (AndFilter) filteredQuery.getFilter();
assertThat(andFilter.filters().size(), equalTo(2));
assertThat(((TermFilter) andFilter.filters().get(0)).getTerm(), equalTo(new Term("name.first", "shay1")));
assertThat(((TermFilter) andFilter.filters().get(1)).getTerm(), equalTo(new Term("name.first", "shay4")));
}
@Test public void testAndFilteredQuery2() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/and-filter2.json");
@ -695,6 +737,31 @@ public class SimpleIndexQueryParserTests {
assertThat(((TermFilter) filteredQuery.getFilter()).getTerm(), equalTo(new Term("name.last", "banon")));
}
@Test public void testTermFilterQuery() throws Exception {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/term-filter.json");
Query parsedQuery = queryParser.parse(query).query();
assertThat(parsedQuery, instanceOf(FilteredQuery.class));
FilteredQuery filteredQuery = (FilteredQuery) parsedQuery;
assertThat(filteredQuery.getFilter(), instanceOf(TermFilter.class));
TermFilter termFilter = (TermFilter) filteredQuery.getFilter();
assertThat(termFilter.getTerm().field(), equalTo("name.last"));
assertThat(termFilter.getTerm().text(), equalTo("banon"));
}
@Test public void testTermNamedFilterQuery() throws Exception {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/term-filter-named.json");
ParsedQuery parsedQuery = queryParser.parse(query);
assertThat(parsedQuery.namedFilters().containsKey("test"), equalTo(true));
assertThat(parsedQuery.query(), instanceOf(FilteredQuery.class));
FilteredQuery filteredQuery = (FilteredQuery) parsedQuery.query();
assertThat(filteredQuery.getFilter(), instanceOf(TermFilter.class));
TermFilter termFilter = (TermFilter) filteredQuery.getFilter();
assertThat(termFilter.getTerm().field(), equalTo("name.last"));
assertThat(termFilter.getTerm().text(), equalTo("banon"));
}
@Test public void testTermsFilterQueryBuilder() throws Exception {
IndexQueryParser queryParser = newQueryParser();
Query parsedQuery = queryParser.parse(filtered(termQuery("name.first", "shay"), termsFilter("name.last", "banon", "kimchy"))).query();
@ -725,6 +792,22 @@ public class SimpleIndexQueryParserTests {
assertThat(terms.iterator().next().text(), equalTo("banon"));
}
@Test public void testTermsWithNameFilterQuery() throws Exception {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/terms-filter-named.json");
ParsedQuery parsedQuery = queryParser.parse(query);
assertThat(parsedQuery.namedFilters().containsKey("test"), equalTo(true));
assertThat(parsedQuery.query(), instanceOf(FilteredQuery.class));
FilteredQuery filteredQuery = (FilteredQuery) parsedQuery.query();
assertThat(filteredQuery.getFilter(), instanceOf(TermsFilter.class));
TermsFilter termsFilter = (TermsFilter) filteredQuery.getFilter();
Field field = TermsFilter.class.getDeclaredField("terms");
field.setAccessible(true);
Set<Term> terms = (Set<Term>) field.get(termsFilter);
assertThat(terms.size(), equalTo(2));
assertThat(terms.iterator().next().text(), equalTo("banon"));
}
@Test public void testConstantScoreQueryBuilder() throws IOException {
IndexQueryParser queryParser = newQueryParser();
Query parsedQuery = queryParser.parse(constantScoreQuery(termFilter("name.last", "banon"))).query();
@ -908,6 +991,21 @@ public class SimpleIndexQueryParserTests {
assertThat(((TermQuery) wrappedQuery).getTerm(), equalTo(new Term("name.last", "banon")));
}
@Test public void testFQueryFilter() throws Exception {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/fquery-filter.json");
ParsedQuery parsedQuery = queryParser.parse(query);
assertThat(parsedQuery.namedFilters().containsKey("test"), equalTo(true));
assertThat(parsedQuery.query(), instanceOf(FilteredQuery.class));
FilteredQuery filteredQuery = (FilteredQuery) parsedQuery.query();
QueryWrapperFilter queryWrapperFilter = (QueryWrapperFilter) filteredQuery.getFilter();
Field field = QueryWrapperFilter.class.getDeclaredField("query");
field.setAccessible(true);
Query wrappedQuery = (Query) field.get(queryWrapperFilter);
assertThat(wrappedQuery, instanceOf(TermQuery.class));
assertThat(((TermQuery) wrappedQuery).getTerm(), equalTo(new Term("name.last", "banon")));
}
@Test public void testMoreLikeThisBuilder() throws Exception {
IndexQueryParser queryParser = newQueryParser();
Query parsedQuery = queryParser.parse(moreLikeThisQuery("name.first", "name.last").likeText("something").minTermFreq(1).maxQueryTerms(12)).query();
@ -985,6 +1083,21 @@ public class SimpleIndexQueryParserTests {
assertThat(mltQuery.getMaxQueryTerms(), equalTo(12));
}
@Test public void testGeoDistanceFilterNamed() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/geo_distance-named.json");
ParsedQuery parsedQuery = queryParser.parse(query);
assertThat(parsedQuery.namedFilters().containsKey("test"), equalTo(true));
assertThat(parsedQuery.query(), instanceOf(FilteredQuery.class));
FilteredQuery filteredQuery = (FilteredQuery) parsedQuery.query();
GeoDistanceFilter filter = (GeoDistanceFilter) filteredQuery.getFilter();
assertThat(filter.latFieldName(), equalTo("location.lat"));
assertThat(filter.lonFieldName(), equalTo("location.lon"));
assertThat(filter.lat(), closeTo(40, 0.00001));
assertThat(filter.lon(), closeTo(-70, 0.00001));
assertThat(filter.distance(), closeTo(12, 0.00001));
}
@Test public void testGeoDistanceFilter1() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/geo_distance1.json");
@ -1041,6 +1154,23 @@ public class SimpleIndexQueryParserTests {
assertThat(filter.distance(), closeTo(12, 0.00001));
}
@Test public void testGeoBoundingBoxFilterNamed() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/geo_boundingbox-named.json");
ParsedQuery parsedQuery = queryParser.parse(query);
assertThat(parsedQuery.query(), instanceOf(FilteredQuery.class));
assertThat(parsedQuery.namedFilters().containsKey("test"), equalTo(true));
FilteredQuery filteredQuery = (FilteredQuery) parsedQuery.query();
GeoBoundingBoxFilter filter = (GeoBoundingBoxFilter) filteredQuery.getFilter();
assertThat(filter.latFieldName(), equalTo("location.lat"));
assertThat(filter.lonFieldName(), equalTo("location.lon"));
assertThat(filter.topLeft().lat, closeTo(40, 0.00001));
assertThat(filter.topLeft().lon, closeTo(-70, 0.00001));
assertThat(filter.bottomRight().lat, closeTo(30, 0.00001));
assertThat(filter.bottomRight().lon, closeTo(-80, 0.00001));
}
@Test public void testGeoBoundingBoxFilter1() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/geo_boundingbox1.json");
@ -1101,6 +1231,25 @@ public class SimpleIndexQueryParserTests {
assertThat(filter.bottomRight().lon, closeTo(-80, 0.00001));
}
@Test public void testGeoPolygonNamedFilter() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/geo_polygon-named.json");
ParsedQuery parsedQuery = queryParser.parse(query);
assertThat(parsedQuery.namedFilters().containsKey("test"), equalTo(true));
assertThat(parsedQuery.query(), instanceOf(FilteredQuery.class));
FilteredQuery filteredQuery = (FilteredQuery) parsedQuery.query();
GeoPolygonFilter filter = (GeoPolygonFilter) filteredQuery.getFilter();
assertThat(filter.latFieldName(), equalTo("location.lat"));
assertThat(filter.lonFieldName(), equalTo("location.lon"));
assertThat(filter.points().length, equalTo(3));
assertThat(filter.points()[0].lat, closeTo(40, 0.00001));
assertThat(filter.points()[0].lon, closeTo(-70, 0.00001));
assertThat(filter.points()[1].lat, closeTo(30, 0.00001));
assertThat(filter.points()[1].lon, closeTo(-80, 0.00001));
assertThat(filter.points()[2].lat, closeTo(20, 0.00001));
assertThat(filter.points()[2].lon, closeTo(-90, 0.00001));
}
@Test public void testGeoPolygonFilter1() throws IOException {
IndexQueryParser queryParser = newQueryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/xcontent/geo_polygon1.json");

View File

@ -0,0 +1,20 @@
{
"filtered" : {
"query" : {
"term" : { "name.first" : "shay" }
},
"filter" : {
"and" : {
"filters" : [
{
"term" : { "name.first" : "shay1" }
},
{
"term" : { "name.first" : "shay4" }
}
],
"_name" : "test"
}
}
}
}

View File

@ -0,0 +1,15 @@
{
"filtered" : {
"query" : {
"term" : { "name.first" : "shay" }
},
"filter" : {
"fquery" : {
"query" : {
"term" : { "name.last" : "banon" }
},
"_name" : "test"
}
}
}
}

View File

@ -0,0 +1,16 @@
{
"filtered" : {
"query" : {
"match_all" : {}
},
"filter" : {
"geo_bounding_box" : {
"person.location" : {
"top_left" : [40, -70],
"bottom_right" : [30, -80]
},
"_name" : "test"
}
}
}
}

View File

@ -0,0 +1,17 @@
{
"filtered" : {
"query" : {
"match_all" : {}
},
"filter" : {
"geo_distance" : {
"distance" : "12mi",
"person.location" : {
"lat" : 40,
"lon" : -70
},
"_name" : "test"
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"filtered" : {
"query" : {
"match_all" : {}
},
"filter" : {
"geo_polygon" : {
"person.location" : {
"points" : [
[40, -70],
[30, -80],
[20, -90]
]
},
"_name" : "test"
}
}
}
}

View File

@ -0,0 +1,13 @@
{
"filtered" : {
"query" : {
"term" : { "name.first" : "shay" }
},
"filter" : {
"prefix" : {
"name.first" : "sh",
"_name" : "test"
}
}
}
}

View File

@ -1,10 +1,10 @@
{
filtered : {
query : {
term : { "name.first" : "shay" }
"filtered" : {
"query" : {
"term" : { "name.first" : "shay" }
},
filter : {
prefix : { "name.first" : "sh" }
"filter" : {
"prefix" : { "name.first" : "sh" }
}
}
}

View File

@ -0,0 +1,13 @@
{
"filtered" : {
"query" : {
"term" : { "name.first" : "shay" }
},
"filter" : {
"range" : {
"age" : { "from" : "23", "to" : "54", "include_lower" : true, "include_upper": false},
"_name" : "test"
}
}
}
}

View File

@ -0,0 +1,13 @@
{
"filtered" : {
"query" : {
"term" : { "name.first" : "shay" }
},
"filter" : {
"term" : {
"name.last" : "banon",
"_name" : "test"
}
}
}
}

View File

@ -0,0 +1,12 @@
{
"filtered" : {
"query" : {
"term" : { "name.first" : "shay" }
},
"filter" : {
"term" : {
"name.last" : "banon"
}
}
}
}

View File

@ -0,0 +1,13 @@
{
"filtered" : {
"query" : {
"term" : { "name.first" : "shay" }
},
"filter" : {
"terms" : {
"name.last" : ["banon", "kimchy"],
"_name" : "test"
}
}
}
}

View File

@ -1,10 +1,10 @@
{
filtered : {
query : {
term : { "name.first" : "shay" }
"filtered" : {
"query" : {
"term" : { "name.first" : "shay" }
},
filter : {
terms : {
"filter" : {
"terms" : {
"name.last" : ["banon", "kimchy"]
}
}

View File

@ -0,0 +1,103 @@
/*
* 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.test.integration.search.matchedfilters;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.test.integration.AbstractNodesTests;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.elasticsearch.common.xcontent.XContentFactory.*;
import static org.elasticsearch.index.query.xcontent.FilterBuilders.*;
import static org.elasticsearch.index.query.xcontent.QueryBuilders.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
/**
* @author kimchy (shay.banon)
*/
public class MatchedFiltersTests extends AbstractNodesTests {
private Client client;
@BeforeClass public void createNodes() throws Exception {
startNode("server1");
startNode("server2");
client = getClient();
}
@AfterClass public void closeNodes() {
client.close();
closeAllNodes();
}
protected Client getClient() {
return client("server1");
}
@Test public void simpleMatchedFilter() throws Exception {
try {
client.admin().indices().prepareDelete("test").execute().actionGet();
} catch (Exception e) {
// ignore
}
client.admin().indices().prepareCreate("test").execute().actionGet();
client.admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
client.prepareIndex("test", "type1", "1").setSource(jsonBuilder().startObject()
.field("name", "test1")
.field("number", 1)
.endObject()).execute().actionGet();
client.prepareIndex("test", "type1", "2").setSource(jsonBuilder().startObject()
.field("name", "test2")
.field("number", 2)
.endObject()).execute().actionGet();
client.admin().indices().prepareFlush().execute().actionGet();
client.prepareIndex("test", "type1", "3").setSource(jsonBuilder().startObject()
.field("name", "test3")
.field("number", 3)
.endObject()).execute().actionGet();
client.admin().indices().prepareRefresh().execute().actionGet();
SearchResponse searchResponse = client.prepareSearch()
.setQuery(filtered(matchAllQuery(), orFilter(rangeFilter("number").lte(2).filterName("test1"), rangeFilter("number").gt(2).filterName("test2"))))
.execute().actionGet();
assertThat(searchResponse.hits().totalHits(), equalTo(3l));
for (SearchHit hit : searchResponse.hits()) {
if (hit.id().equals("1") || hit.id().equals("2")) {
assertThat(hit.matchedFilters().length, equalTo(1));
assertThat(hit.matchedFilters(), hasItemInArray("test1"));
} else if (hit.id().equals("3")) {
assertThat(hit.matchedFilters().length, equalTo(1));
assertThat(hit.matchedFilters(), hasItemInArray("test2"));
} else {
assert false;
}
}
}
}