Query DSL: queryString - allow to run against multiple fields, closes #48.
This commit is contained in:
parent
fbf9197b85
commit
fdd221e8ea
|
@ -35,7 +35,7 @@ import java.io.IOException;
|
|||
import java.util.List;
|
||||
|
||||
import static com.google.common.collect.Lists.*;
|
||||
import static org.elasticsearch.index.query.support.QueryParsers.*;
|
||||
import static org.elasticsearch.util.lucene.search.Queries.*;
|
||||
|
||||
/**
|
||||
* @author kimchy (Shay Banon)
|
||||
|
|
|
@ -77,6 +77,8 @@ public class DisMaxJsonQueryParser extends AbstractIndexComponent implements Jso
|
|||
boost = jp.getFloatValue();
|
||||
} else if ("tieBreakerMultiplier".equals(currentFieldName)) {
|
||||
tieBreakerMultiplier = jp.getFloatValue();
|
||||
} else if ("tieBreaker".equals(currentFieldName)) {
|
||||
tieBreakerMultiplier = jp.getFloatValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ import org.elasticsearch.util.settings.Settings;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.elasticsearch.index.query.support.QueryParsers.*;
|
||||
import static org.elasticsearch.util.lucene.search.Queries.*;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
|
|
|
@ -20,8 +20,12 @@
|
|||
package org.elasticsearch.index.query.json;
|
||||
|
||||
import org.elasticsearch.util.json.JsonBuilder;
|
||||
import org.elasticsearch.util.trove.ExtTObjectFloatHashMap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.collect.Lists.*;
|
||||
|
||||
/**
|
||||
* @author kimchy (Shay Banon)
|
||||
|
@ -55,6 +59,14 @@ public class QueryStringJsonQueryBuilder extends BaseJsonQueryBuilder {
|
|||
|
||||
private int phraseSlop = -1;
|
||||
|
||||
private List<String> fields;
|
||||
|
||||
private ExtTObjectFloatHashMap<String> fieldsBoosts;
|
||||
|
||||
private Boolean useDisMax;
|
||||
|
||||
private float tieBreaker = -1;
|
||||
|
||||
public QueryStringJsonQueryBuilder(String queryString) {
|
||||
this.queryString = queryString;
|
||||
}
|
||||
|
@ -64,6 +76,50 @@ public class QueryStringJsonQueryBuilder extends BaseJsonQueryBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field to run the query string against.
|
||||
*/
|
||||
public QueryStringJsonQueryBuilder field(String field) {
|
||||
if (fields == null) {
|
||||
fields = newArrayList();
|
||||
}
|
||||
fields.add(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field to run the query string against with a specific boost.
|
||||
*/
|
||||
public QueryStringJsonQueryBuilder field(String field, float boost) {
|
||||
if (fields == null) {
|
||||
fields = newArrayList();
|
||||
}
|
||||
fields.add(field);
|
||||
if (fieldsBoosts == null) {
|
||||
fieldsBoosts = new ExtTObjectFloatHashMap<String>().defaultReturnValue(-1);
|
||||
}
|
||||
fieldsBoosts.put(field, boost);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When more than one field is used with the query string, should queries be combined using
|
||||
* dis max, or boolean query. Defaults to dis max (<tt>true</tt>).
|
||||
*/
|
||||
public QueryStringJsonQueryBuilder useDisMax(boolean useDisMax) {
|
||||
this.useDisMax = useDisMax;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When more than one field is used with the query string, and combined queries are using
|
||||
* dis max, control the tie breaker for it.
|
||||
*/
|
||||
public QueryStringJsonQueryBuilder tieBreaker(float tieBreaker) {
|
||||
this.tieBreaker = tieBreaker;
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryStringJsonQueryBuilder defaultOperator(Operator defaultOperator) {
|
||||
this.defaultOperator = defaultOperator;
|
||||
return this;
|
||||
|
@ -115,6 +171,26 @@ public class QueryStringJsonQueryBuilder extends BaseJsonQueryBuilder {
|
|||
if (defaultField != null) {
|
||||
builder.field("defaultField", defaultField);
|
||||
}
|
||||
if (fields != null) {
|
||||
builder.startArray("fields");
|
||||
for (String field : fields) {
|
||||
float boost = -1;
|
||||
if (fieldsBoosts != null) {
|
||||
boost = fieldsBoosts.get(field);
|
||||
}
|
||||
if (boost != -1) {
|
||||
field += "^" + boost;
|
||||
}
|
||||
builder.string(field);
|
||||
}
|
||||
builder.endArray();
|
||||
}
|
||||
if (useDisMax != null) {
|
||||
builder.field("useDisMax", useDisMax);
|
||||
}
|
||||
if (tieBreaker != -1) {
|
||||
builder.field("tieBreaker", tieBreaker);
|
||||
}
|
||||
if (defaultOperator != null) {
|
||||
builder.field("defaultOperator", defaultOperator.name().toLowerCase());
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package org.elasticsearch.index.query.json;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
import org.apache.lucene.analysis.Analyzer;
|
||||
import org.apache.lucene.queryParser.ParseException;
|
||||
|
@ -32,12 +33,15 @@ 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.query.support.MultiFieldMapperQueryParser;
|
||||
import org.elasticsearch.index.settings.IndexSettings;
|
||||
import org.elasticsearch.util.settings.Settings;
|
||||
import org.elasticsearch.util.trove.ExtTObjectFloatHashMap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.index.query.support.QueryParsers.*;
|
||||
import static org.elasticsearch.util.lucene.search.Queries.*;
|
||||
|
||||
/**
|
||||
* @author kimchy (Shay Banon)
|
||||
|
@ -74,12 +78,46 @@ public class QueryStringJsonQueryParser extends AbstractIndexComponent implement
|
|||
float boost = 1.0f;
|
||||
boolean escape = false;
|
||||
Analyzer analyzer = null;
|
||||
List<String> fields = null;
|
||||
ExtTObjectFloatHashMap<String> boosts = null;
|
||||
float tieBreaker = 0.0f;
|
||||
boolean useDisMax = true;
|
||||
|
||||
String currentFieldName = null;
|
||||
JsonToken token;
|
||||
while ((token = jp.nextToken()) != JsonToken.END_OBJECT) {
|
||||
if (token == JsonToken.FIELD_NAME) {
|
||||
currentFieldName = jp.getCurrentName();
|
||||
} else if (token == JsonToken.START_ARRAY) {
|
||||
if ("fields".equals(currentFieldName)) {
|
||||
while ((token = jp.nextToken()) != JsonToken.END_ARRAY) {
|
||||
String fField = null;
|
||||
float fBoost = -1;
|
||||
char[] text = jp.getTextCharacters();
|
||||
int end = jp.getTextOffset() + jp.getTextLength();
|
||||
for (int i = jp.getTextOffset(); i < end; i++) {
|
||||
if (text[i] == '^') {
|
||||
int relativeLocation = i - jp.getTextOffset();
|
||||
fField = new String(text, jp.getTextOffset(), relativeLocation);
|
||||
fBoost = Float.parseFloat(new String(text, i + 1, jp.getTextLength() - relativeLocation - 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fField == null) {
|
||||
fField = jp.getText();
|
||||
}
|
||||
if (fields == null) {
|
||||
fields = Lists.newArrayList();
|
||||
}
|
||||
fields.add(fField);
|
||||
if (fBoost != -1) {
|
||||
if (boosts == null) {
|
||||
boosts = new ExtTObjectFloatHashMap<String>();
|
||||
}
|
||||
boosts.put(fField, fBoost);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (token == JsonToken.VALUE_STRING) {
|
||||
if ("query".equals(currentFieldName)) {
|
||||
queryString = jp.getText();
|
||||
|
@ -106,12 +144,16 @@ public class QueryStringJsonQueryParser extends AbstractIndexComponent implement
|
|||
enablePositionIncrements = token == JsonToken.VALUE_TRUE;
|
||||
} else if ("escape".equals(currentFieldName)) {
|
||||
escape = token == JsonToken.VALUE_TRUE;
|
||||
} else if ("useDisMax".equals(currentFieldName)) {
|
||||
useDisMax = token == JsonToken.VALUE_TRUE;
|
||||
}
|
||||
} else if (token == JsonToken.VALUE_NUMBER_FLOAT) {
|
||||
if ("fuzzyMinSim".equals(currentFieldName)) {
|
||||
fuzzyMinSim = jp.getFloatValue();
|
||||
} else if ("boost".equals(currentFieldName)) {
|
||||
boost = jp.getFloatValue();
|
||||
} else if ("tieBreaker".equals(currentFieldName)) {
|
||||
tieBreaker = jp.getFloatValue();
|
||||
}
|
||||
} else if (token == JsonToken.VALUE_NUMBER_INT) {
|
||||
if ("fuzzyPrefixLength".equals(currentFieldName)) {
|
||||
|
@ -130,6 +172,10 @@ public class QueryStringJsonQueryParser extends AbstractIndexComponent implement
|
|||
enablePositionIncrements = jp.getIntValue() != 0;
|
||||
} else if ("escape".equals(currentFieldName)) {
|
||||
escape = jp.getIntValue() != 0;
|
||||
} else if ("useDisMax".equals(currentFieldName)) {
|
||||
escape = jp.getIntValue() != 0;
|
||||
} else if ("tieBreaker".equals(currentFieldName)) {
|
||||
tieBreaker = jp.getFloatValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,7 +186,19 @@ public class QueryStringJsonQueryParser extends AbstractIndexComponent implement
|
|||
analyzer = parseContext.mapperService().searchAnalyzer();
|
||||
}
|
||||
|
||||
MapperQueryParser queryParser = new MapperQueryParser(defaultField, analyzer, parseContext.mapperService(), parseContext.filterCache());
|
||||
MapperQueryParser queryParser;
|
||||
if (fields != null) {
|
||||
if (fields.size() == 1) {
|
||||
queryParser = new MapperQueryParser(fields.get(0), analyzer, parseContext.mapperService(), parseContext.filterCache());
|
||||
} else {
|
||||
MultiFieldMapperQueryParser mQueryParser = new MultiFieldMapperQueryParser(fields, boosts, analyzer, parseContext.mapperService(), parseContext.filterCache());
|
||||
mQueryParser.setTieBreaker(tieBreaker);
|
||||
mQueryParser.setUseDisMax(useDisMax);
|
||||
queryParser = mQueryParser;
|
||||
}
|
||||
} else {
|
||||
queryParser = new MapperQueryParser(defaultField, analyzer, parseContext.mapperService(), parseContext.filterCache());
|
||||
}
|
||||
queryParser.setEnablePositionIncrements(enablePositionIncrements);
|
||||
queryParser.setLowercaseExpandedTerms(lowercaseExpandedTerms);
|
||||
queryParser.setAllowLeadingWildcard(allowLeadingWildcard);
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.elasticsearch.util.Nullable;
|
|||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.index.query.support.QueryParsers.*;
|
||||
import static org.elasticsearch.util.lucene.search.Queries.*;
|
||||
|
||||
/**
|
||||
* A query parser that uses the {@link MapperService} in order to build smarter
|
||||
|
@ -145,7 +146,7 @@ public class MapperQueryParser extends QueryParser {
|
|||
if (q == null) {
|
||||
return null;
|
||||
}
|
||||
return fixNegativeQueryIfNeeded(q);
|
||||
return optimizeQuery(fixNegativeQueryIfNeeded(q));
|
||||
}
|
||||
|
||||
protected FieldMapper fieldMapper(String smartName) {
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* 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.support;
|
||||
|
||||
import org.apache.lucene.analysis.Analyzer;
|
||||
import org.apache.lucene.queryParser.ParseException;
|
||||
import org.apache.lucene.search.*;
|
||||
import org.elasticsearch.index.cache.filter.FilterCache;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.util.Nullable;
|
||||
import org.elasticsearch.util.trove.ExtTObjectFloatHashMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
public class MultiFieldMapperQueryParser extends MapperQueryParser {
|
||||
|
||||
private List<String> fields;
|
||||
|
||||
private ExtTObjectFloatHashMap<String> boosts;
|
||||
|
||||
private float tieBreaker = 0.0f;
|
||||
|
||||
private boolean useDisMax = true;
|
||||
|
||||
public MultiFieldMapperQueryParser(List<String> fields, @Nullable ExtTObjectFloatHashMap<String> boosts, Analyzer analyzer, @Nullable MapperService mapperService, @Nullable FilterCache filterCache) {
|
||||
super(null, analyzer, mapperService, filterCache);
|
||||
this.fields = fields;
|
||||
this.boosts = boosts;
|
||||
if (this.boosts != null) {
|
||||
boosts.defaultReturnValue(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTieBreaker(float tieBreaker) {
|
||||
this.tieBreaker = tieBreaker;
|
||||
}
|
||||
|
||||
public void setUseDisMax(boolean useDisMax) {
|
||||
this.useDisMax = useDisMax;
|
||||
}
|
||||
|
||||
@Override public Query getFieldQuery(String field, String queryText) throws ParseException {
|
||||
return getFieldQuery(field, queryText, 0);
|
||||
}
|
||||
|
||||
@Override public Query getFieldQuery(String xField, String queryText, int slop) throws ParseException {
|
||||
if (xField != null) {
|
||||
Query q = super.getFieldQuery(xField, queryText);
|
||||
applySlop(q, slop);
|
||||
return q;
|
||||
}
|
||||
if (useDisMax) {
|
||||
DisjunctionMaxQuery disMaxQuery = new DisjunctionMaxQuery(tieBreaker);
|
||||
boolean added = false;
|
||||
for (String field : fields) {
|
||||
Query q = super.getFieldQuery(field, queryText);
|
||||
if (q != null) {
|
||||
added = true;
|
||||
applyBoost(field, q);
|
||||
applySlop(q, slop);
|
||||
disMaxQuery.add(q);
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
return null;
|
||||
}
|
||||
return disMaxQuery;
|
||||
} else {
|
||||
List<BooleanClause> clauses = new ArrayList<BooleanClause>();
|
||||
for (String field : fields) {
|
||||
Query q = super.getFieldQuery(field, queryText);
|
||||
if (q != null) {
|
||||
applyBoost(field, q);
|
||||
applySlop(q, slop);
|
||||
clauses.add(new BooleanClause(q, BooleanClause.Occur.SHOULD));
|
||||
}
|
||||
}
|
||||
if (clauses.size() == 0) // happens for stopwords
|
||||
return null;
|
||||
return getBooleanQuery(clauses, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected Query getRangeQuery(String xField, String part1, String part2, boolean inclusive) throws ParseException {
|
||||
if (xField != null) {
|
||||
return super.getRangeQuery(xField, part1, part2, inclusive);
|
||||
}
|
||||
if (useDisMax) {
|
||||
DisjunctionMaxQuery disMaxQuery = new DisjunctionMaxQuery(tieBreaker);
|
||||
boolean added = false;
|
||||
for (String field : fields) {
|
||||
Query q = super.getRangeQuery(field, part1, part2, inclusive);
|
||||
if (q != null) {
|
||||
added = true;
|
||||
applyBoost(field, q);
|
||||
disMaxQuery.add(q);
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
return null;
|
||||
}
|
||||
return disMaxQuery;
|
||||
} else {
|
||||
List<BooleanClause> clauses = new ArrayList<BooleanClause>();
|
||||
for (String field : fields) {
|
||||
Query q = super.getRangeQuery(field, part1, part2, inclusive);
|
||||
if (q != null) {
|
||||
applyBoost(field, q);
|
||||
clauses.add(new BooleanClause(q, BooleanClause.Occur.SHOULD));
|
||||
}
|
||||
}
|
||||
if (clauses.size() == 0) // happens for stopwords
|
||||
return null;
|
||||
return getBooleanQuery(clauses, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected Query getPrefixQuery(String xField, String termStr) throws ParseException {
|
||||
if (xField != null) {
|
||||
return super.getPrefixQuery(xField, termStr);
|
||||
}
|
||||
if (useDisMax) {
|
||||
DisjunctionMaxQuery disMaxQuery = new DisjunctionMaxQuery(tieBreaker);
|
||||
boolean added = false;
|
||||
for (String field : fields) {
|
||||
Query q = super.getPrefixQuery(field, termStr);
|
||||
if (q != null) {
|
||||
added = true;
|
||||
applyBoost(field, q);
|
||||
disMaxQuery.add(q);
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
return null;
|
||||
}
|
||||
return disMaxQuery;
|
||||
} else {
|
||||
List<BooleanClause> clauses = new ArrayList<BooleanClause>();
|
||||
for (String field : fields) {
|
||||
Query q = super.getPrefixQuery(field, termStr);
|
||||
if (q != null) {
|
||||
applyBoost(field, q);
|
||||
clauses.add(new BooleanClause(q, BooleanClause.Occur.SHOULD));
|
||||
}
|
||||
}
|
||||
if (clauses.size() == 0) // happens for stopwords
|
||||
return null;
|
||||
return getBooleanQuery(clauses, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected Query getWildcardQuery(String xField, String termStr) throws ParseException {
|
||||
if (xField != null) {
|
||||
return super.getWildcardQuery(xField, termStr);
|
||||
}
|
||||
if (useDisMax) {
|
||||
DisjunctionMaxQuery disMaxQuery = new DisjunctionMaxQuery(tieBreaker);
|
||||
boolean added = false;
|
||||
for (String field : fields) {
|
||||
Query q = super.getWildcardQuery(field, termStr);
|
||||
if (q != null) {
|
||||
added = true;
|
||||
applyBoost(field, q);
|
||||
disMaxQuery.add(q);
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
return null;
|
||||
}
|
||||
return disMaxQuery;
|
||||
} else {
|
||||
List<BooleanClause> clauses = new ArrayList<BooleanClause>();
|
||||
for (String field : fields) {
|
||||
Query q = super.getWildcardQuery(field, termStr);
|
||||
if (q != null) {
|
||||
applyBoost(field, q);
|
||||
clauses.add(new BooleanClause(q, BooleanClause.Occur.SHOULD));
|
||||
}
|
||||
}
|
||||
if (clauses.size() == 0) // happens for stopwords
|
||||
return null;
|
||||
return getBooleanQuery(clauses, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected Query getFuzzyQuery(String xField, String termStr, float minSimilarity) throws ParseException {
|
||||
if (xField != null) {
|
||||
return super.getFuzzyQuery(xField, termStr, minSimilarity);
|
||||
}
|
||||
if (useDisMax) {
|
||||
DisjunctionMaxQuery disMaxQuery = new DisjunctionMaxQuery(tieBreaker);
|
||||
boolean added = false;
|
||||
for (String field : fields) {
|
||||
Query q = super.getFuzzyQuery(field, termStr, minSimilarity);
|
||||
if (q != null) {
|
||||
added = true;
|
||||
applyBoost(field, q);
|
||||
disMaxQuery.add(q);
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
return null;
|
||||
}
|
||||
return disMaxQuery;
|
||||
} else {
|
||||
List<BooleanClause> clauses = new ArrayList<BooleanClause>();
|
||||
for (String field : fields) {
|
||||
Query q = super.getFuzzyQuery(field, termStr, minSimilarity);
|
||||
applyBoost(field, q);
|
||||
clauses.add(new BooleanClause(q, BooleanClause.Occur.SHOULD));
|
||||
}
|
||||
return getBooleanQuery(clauses, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyBoost(String field, Query q) {
|
||||
if (boosts != null) {
|
||||
float boost = boosts.get(field);
|
||||
q.setBoost(boost);
|
||||
}
|
||||
}
|
||||
|
||||
private void applySlop(Query q, int slop) {
|
||||
if (q instanceof PhraseQuery) {
|
||||
((PhraseQuery) q).setSlop(slop);
|
||||
} else if (q instanceof MultiPhraseQuery) {
|
||||
((MultiPhraseQuery) q).setSlop(slop);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,8 +26,6 @@ import org.elasticsearch.index.mapper.MapperService;
|
|||
import org.elasticsearch.util.Nullable;
|
||||
import org.elasticsearch.util.lucene.search.TermFilter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
|
@ -37,58 +35,6 @@ public final class QueryParsers {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimizes the given query and returns the optimized version of it.
|
||||
*/
|
||||
public static Query optimizeQuery(Query q) {
|
||||
if (q instanceof BooleanQuery) {
|
||||
return optimizeBooleanQuery((BooleanQuery) q);
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
public static BooleanQuery optimizeBooleanQuery(BooleanQuery q) {
|
||||
BooleanQuery optimized = new BooleanQuery(q.isCoordDisabled());
|
||||
optimized.setMinimumNumberShouldMatch(q.getMinimumNumberShouldMatch());
|
||||
optimizeBooleanQuery(optimized, q);
|
||||
return optimized;
|
||||
}
|
||||
|
||||
public static void optimizeBooleanQuery(BooleanQuery optimized, BooleanQuery q) {
|
||||
for (BooleanClause clause : q.clauses()) {
|
||||
Query cq = clause.getQuery();
|
||||
cq.setBoost(cq.getBoost() * q.getBoost());
|
||||
if (cq instanceof BooleanQuery && !clause.isRequired() && !clause.isProhibited()) {
|
||||
optimizeBooleanQuery(optimized, (BooleanQuery) cq);
|
||||
} else {
|
||||
optimized.add(clause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isNegativeQuery(Query q) {
|
||||
if (!(q instanceof BooleanQuery)) {
|
||||
return false;
|
||||
}
|
||||
List<BooleanClause> clauses = ((BooleanQuery) q).clauses();
|
||||
if (clauses.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (BooleanClause clause : clauses) {
|
||||
if (!clause.isProhibited()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Query fixNegativeQueryIfNeeded(Query q) {
|
||||
if (isNegativeQuery(q)) {
|
||||
BooleanQuery newBq = (BooleanQuery) q.clone();
|
||||
newBq.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST);
|
||||
return newBq;
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
public static Query wrapSmartNameQuery(Query query, @Nullable MapperService.SmartNameFieldMappers smartFieldMappers,
|
||||
@Nullable FilterCache filterCache) {
|
||||
if (smartFieldMappers == null) {
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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.util.lucene.search;
|
||||
|
||||
import org.apache.lucene.search.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
public class Queries {
|
||||
|
||||
private final static Field disjuncts;
|
||||
|
||||
static {
|
||||
Field disjunctsX;
|
||||
try {
|
||||
disjunctsX = DisjunctionMaxQuery.class.getDeclaredField("disjuncts");
|
||||
disjunctsX.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
disjunctsX = null;
|
||||
}
|
||||
disjuncts = disjunctsX;
|
||||
}
|
||||
|
||||
public static List<Query> disMaxClauses(DisjunctionMaxQuery query) {
|
||||
try {
|
||||
return (List<Query>) disjuncts.get(query);
|
||||
} catch (IllegalAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimizes the given query and returns the optimized version of it.
|
||||
*/
|
||||
public static Query optimizeQuery(Query q) {
|
||||
if (q instanceof BooleanQuery) {
|
||||
return optimizeBooleanQuery((BooleanQuery) q);
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
public static BooleanQuery optimizeBooleanQuery(BooleanQuery q) {
|
||||
BooleanQuery optimized = new BooleanQuery(q.isCoordDisabled());
|
||||
optimized.setMinimumNumberShouldMatch(q.getMinimumNumberShouldMatch());
|
||||
optimizeBooleanQuery(optimized, q);
|
||||
return optimized;
|
||||
}
|
||||
|
||||
public static void optimizeBooleanQuery(BooleanQuery optimized, BooleanQuery q) {
|
||||
for (BooleanClause clause : q.clauses()) {
|
||||
Query cq = clause.getQuery();
|
||||
cq.setBoost(cq.getBoost() * q.getBoost());
|
||||
if (cq instanceof BooleanQuery && !clause.isRequired() && !clause.isProhibited()) {
|
||||
optimizeBooleanQuery(optimized, (BooleanQuery) cq);
|
||||
} else {
|
||||
optimized.add(clause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isNegativeQuery(Query q) {
|
||||
if (!(q instanceof BooleanQuery)) {
|
||||
return false;
|
||||
}
|
||||
List<BooleanClause> clauses = ((BooleanQuery) q).clauses();
|
||||
if (clauses.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (BooleanClause clause : clauses) {
|
||||
if (!clause.isProhibited()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Query fixNegativeQueryIfNeeded(Query q) {
|
||||
if (isNegativeQuery(q)) {
|
||||
BooleanQuery newBq = (BooleanQuery) q.clone();
|
||||
newBq.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST);
|
||||
return newBq;
|
||||
}
|
||||
return q;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.util.trove;
|
||||
|
||||
import org.elasticsearch.util.gnu.trove.TObjectFloatHashMap;
|
||||
import org.elasticsearch.util.gnu.trove.TObjectHashingStrategy;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
public class ExtTObjectFloatHashMap<T> extends TObjectFloatHashMap<T> {
|
||||
|
||||
private float defaultReturnValue = 0;
|
||||
|
||||
public ExtTObjectFloatHashMap() {
|
||||
}
|
||||
|
||||
public ExtTObjectFloatHashMap(int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
}
|
||||
|
||||
public ExtTObjectFloatHashMap(int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
public ExtTObjectFloatHashMap(TObjectHashingStrategy<T> ttObjectHashingStrategy) {
|
||||
super(ttObjectHashingStrategy);
|
||||
}
|
||||
|
||||
public ExtTObjectFloatHashMap(int initialCapacity, TObjectHashingStrategy<T> ttObjectHashingStrategy) {
|
||||
super(initialCapacity, ttObjectHashingStrategy);
|
||||
}
|
||||
|
||||
public ExtTObjectFloatHashMap(int initialCapacity, float loadFactor, TObjectHashingStrategy<T> ttObjectHashingStrategy) {
|
||||
super(initialCapacity, loadFactor, ttObjectHashingStrategy);
|
||||
}
|
||||
|
||||
public ExtTObjectFloatHashMap<T> defaultReturnValue(float defaultReturnValue) {
|
||||
this.defaultReturnValue = defaultReturnValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override public float get(T key) {
|
||||
int index = index(key);
|
||||
return index < 0 ? defaultReturnValue : _values[index];
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ import org.elasticsearch.index.cache.filter.none.NoneFilterCache;
|
|||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.index.query.IndexQueryParser;
|
||||
import org.elasticsearch.util.lucene.search.MoreLikeThisQuery;
|
||||
import org.elasticsearch.util.lucene.search.Queries;
|
||||
import org.elasticsearch.util.lucene.search.TermFilter;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
@ -71,6 +72,73 @@ public class SimpleJsonIndexQueryParserTests {
|
|||
assertThat(termQuery.getTerm(), equalTo(new Term("content", "test")));
|
||||
}
|
||||
|
||||
@Test public void testQueryStringFields1Builder() throws Exception {
|
||||
IndexQueryParser queryParser = newQueryParser();
|
||||
Query parsedQuery = queryParser.parse(queryString("test").field("content").field("name").useDisMax(false));
|
||||
assertThat(parsedQuery, instanceOf(BooleanQuery.class));
|
||||
BooleanQuery bQuery = (BooleanQuery) parsedQuery;
|
||||
assertThat(bQuery.clauses().size(), equalTo(2));
|
||||
assertThat(((TermQuery) bQuery.clauses().get(0).getQuery()).getTerm(), equalTo(new Term("content", "test")));
|
||||
assertThat(((TermQuery) bQuery.clauses().get(1).getQuery()).getTerm(), equalTo(new Term("name", "test")));
|
||||
}
|
||||
|
||||
@Test public void testQueryStringFields1() throws Exception {
|
||||
IndexQueryParser queryParser = newQueryParser();
|
||||
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/json/query-fields1.json");
|
||||
Query parsedQuery = queryParser.parse(query);
|
||||
assertThat(parsedQuery, instanceOf(BooleanQuery.class));
|
||||
BooleanQuery bQuery = (BooleanQuery) parsedQuery;
|
||||
assertThat(bQuery.clauses().size(), equalTo(2));
|
||||
assertThat(((TermQuery) bQuery.clauses().get(0).getQuery()).getTerm(), equalTo(new Term("content", "test")));
|
||||
assertThat(((TermQuery) bQuery.clauses().get(1).getQuery()).getTerm(), equalTo(new Term("name", "test")));
|
||||
}
|
||||
|
||||
@Test public void testQueryStringFields2Builder() throws Exception {
|
||||
IndexQueryParser queryParser = newQueryParser();
|
||||
Query parsedQuery = queryParser.parse(queryString("test").field("content").field("name").useDisMax(true));
|
||||
assertThat(parsedQuery, instanceOf(DisjunctionMaxQuery.class));
|
||||
DisjunctionMaxQuery disMaxQuery = (DisjunctionMaxQuery) parsedQuery;
|
||||
List<Query> disjuncts = Queries.disMaxClauses(disMaxQuery);
|
||||
assertThat(((TermQuery) disjuncts.get(0)).getTerm(), equalTo(new Term("content", "test")));
|
||||
assertThat(((TermQuery) disjuncts.get(1)).getTerm(), equalTo(new Term("name", "test")));
|
||||
}
|
||||
|
||||
@Test public void testQueryStringFields2() throws Exception {
|
||||
IndexQueryParser queryParser = newQueryParser();
|
||||
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/json/query-fields2.json");
|
||||
Query parsedQuery = queryParser.parse(query);
|
||||
assertThat(parsedQuery, instanceOf(DisjunctionMaxQuery.class));
|
||||
DisjunctionMaxQuery disMaxQuery = (DisjunctionMaxQuery) parsedQuery;
|
||||
List<Query> disjuncts = Queries.disMaxClauses(disMaxQuery);
|
||||
assertThat(((TermQuery) disjuncts.get(0)).getTerm(), equalTo(new Term("content", "test")));
|
||||
assertThat(((TermQuery) disjuncts.get(1)).getTerm(), equalTo(new Term("name", "test")));
|
||||
}
|
||||
|
||||
@Test public void testQueryStringFields3Builder() throws Exception {
|
||||
IndexQueryParser queryParser = newQueryParser();
|
||||
Query parsedQuery = queryParser.parse(queryString("test").field("content", 2.2f).field("name").useDisMax(true));
|
||||
assertThat(parsedQuery, instanceOf(DisjunctionMaxQuery.class));
|
||||
DisjunctionMaxQuery disMaxQuery = (DisjunctionMaxQuery) parsedQuery;
|
||||
List<Query> disjuncts = Queries.disMaxClauses(disMaxQuery);
|
||||
assertThat(((TermQuery) disjuncts.get(0)).getTerm(), equalTo(new Term("content", "test")));
|
||||
assertThat((double) disjuncts.get(0).getBoost(), closeTo(2.2, 0.01));
|
||||
assertThat(((TermQuery) disjuncts.get(1)).getTerm(), equalTo(new Term("name", "test")));
|
||||
assertThat((double) disjuncts.get(1).getBoost(), closeTo(1, 0.01));
|
||||
}
|
||||
|
||||
@Test public void testQueryStringFields3() throws Exception {
|
||||
IndexQueryParser queryParser = newQueryParser();
|
||||
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/json/query-fields3.json");
|
||||
Query parsedQuery = queryParser.parse(query);
|
||||
assertThat(parsedQuery, instanceOf(DisjunctionMaxQuery.class));
|
||||
DisjunctionMaxQuery disMaxQuery = (DisjunctionMaxQuery) parsedQuery;
|
||||
List<Query> disjuncts = Queries.disMaxClauses(disMaxQuery);
|
||||
assertThat(((TermQuery) disjuncts.get(0)).getTerm(), equalTo(new Term("content", "test")));
|
||||
assertThat((double) disjuncts.get(0).getBoost(), closeTo(2.2, 0.01));
|
||||
assertThat(((TermQuery) disjuncts.get(1)).getTerm(), equalTo(new Term("name", "test")));
|
||||
assertThat((double) disjuncts.get(1).getBoost(), closeTo(1, 0.01));
|
||||
}
|
||||
|
||||
@Test public void testMatchAllBuilder() throws Exception {
|
||||
IndexQueryParser queryParser = newQueryParser();
|
||||
Query parsedQuery = queryParser.parse(matchAllQuery().boost(1.2f).buildAsString());
|
||||
|
@ -95,9 +163,7 @@ public class SimpleJsonIndexQueryParserTests {
|
|||
DisjunctionMaxQuery disjunctionMaxQuery = (DisjunctionMaxQuery) parsedQuery;
|
||||
assertThat((double) disjunctionMaxQuery.getBoost(), closeTo(1.2, 0.01));
|
||||
|
||||
Field field = disjunctionMaxQuery.getClass().getDeclaredField("disjuncts");
|
||||
field.setAccessible(true);
|
||||
List<Query> disjuncts = (List<Query>) field.get(disjunctionMaxQuery);
|
||||
List<Query> disjuncts = Queries.disMaxClauses(disjunctionMaxQuery);
|
||||
assertThat(disjuncts.size(), equalTo(2));
|
||||
|
||||
Query firstQ = disjuncts.get(0);
|
||||
|
@ -117,9 +183,7 @@ public class SimpleJsonIndexQueryParserTests {
|
|||
DisjunctionMaxQuery disjunctionMaxQuery = (DisjunctionMaxQuery) parsedQuery;
|
||||
assertThat((double) disjunctionMaxQuery.getBoost(), closeTo(1.2, 0.01));
|
||||
|
||||
Field field = disjunctionMaxQuery.getClass().getDeclaredField("disjuncts");
|
||||
field.setAccessible(true);
|
||||
List<Query> disjuncts = (List<Query>) field.get(disjunctionMaxQuery);
|
||||
List<Query> disjuncts = Queries.disMaxClauses(disjunctionMaxQuery);
|
||||
assertThat(disjuncts.size(), equalTo(2));
|
||||
|
||||
Query firstQ = disjuncts.get(0);
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
queryString : {
|
||||
fields : ["content", "name"],
|
||||
useDisMax : false,
|
||||
query: "test"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
queryString : {
|
||||
fields : ["content", "name"],
|
||||
useDisMax : true,
|
||||
query: "test"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
queryString : {
|
||||
fields : ["content^2.2", "name"],
|
||||
useDisMax : true,
|
||||
query: "test"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue