mirror of https://github.com/apache/lucene.git
SOLR-14539: Introducing {!bool excludeTags=foo,bar}
This commit is contained in:
parent
20c1fdbf7e
commit
f647400e31
|
@ -172,6 +172,8 @@ Improvements
|
|||
* SOLR-13286: Metrics will no longer write a (sometimes large) log message every minute. This can be re-enabled
|
||||
via log4j2.xml if desired, or other HttpSolrCall log messages may be quieted on a per handler basis. (Gus Heck)
|
||||
|
||||
* SOLR-14539: Introducing {!bool excludeTags=...} for Query DSL. (Mikhail Khludnev)
|
||||
|
||||
Optimizations
|
||||
---------------------
|
||||
* SOLR-8306: Do not collect expand documents when expand.rows=0 (Marshall Sanders, Amelia Henderson)
|
||||
|
|
|
@ -23,6 +23,10 @@ import org.apache.lucene.search.Query;
|
|||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.query.FilterQuery;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
import org.apache.solr.search.join.FiltersQParser;
|
||||
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Create a boolean query from sub queries.
|
||||
|
@ -35,33 +39,43 @@ public class BoolQParserPlugin extends QParserPlugin {
|
|||
|
||||
@Override
|
||||
public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
|
||||
return new QParser(qstr, localParams, params, req) {
|
||||
return new FiltersQParser(qstr, localParams, params, req) {
|
||||
@Override
|
||||
public Query parse() throws SyntaxError {
|
||||
BooleanQuery.Builder builder = new BooleanQuery.Builder();
|
||||
SolrParams solrParams = SolrParams.wrapDefaults(localParams, params);
|
||||
addQueries(builder, solrParams.getParams("must"), BooleanClause.Occur.MUST);
|
||||
addQueries(builder, solrParams.getParams("must_not"), BooleanClause.Occur.MUST_NOT);
|
||||
addQueries(builder, solrParams.getParams("filter"), BooleanClause.Occur.FILTER);
|
||||
addQueries(builder, solrParams.getParams("should"), BooleanClause.Occur.SHOULD);
|
||||
return builder.build();
|
||||
return parseImpl();
|
||||
}
|
||||
|
||||
private void addQueries(BooleanQuery.Builder builder, String[] subQueries, BooleanClause.Occur occur) throws SyntaxError {
|
||||
@Override
|
||||
protected Query unwrapQuery(Query query, BooleanClause.Occur occur) {
|
||||
if (occur== BooleanClause.Occur.FILTER) {
|
||||
if (!(query instanceof ExtendedQuery) || (
|
||||
((ExtendedQuery) query).getCache())) {
|
||||
return new FilterQuery(query);
|
||||
}
|
||||
} else {
|
||||
if (query instanceof WrappedQuery) {
|
||||
return ((WrappedQuery)query).getWrappedQuery();
|
||||
}
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<QParser, BooleanClause.Occur> clauses() throws SyntaxError {
|
||||
Map<QParser, BooleanClause.Occur> clauses = new IdentityHashMap<>();
|
||||
SolrParams solrParams = SolrParams.wrapDefaults(localParams, params);
|
||||
addQueries(clauses, solrParams.getParams("must"), BooleanClause.Occur.MUST);
|
||||
addQueries(clauses, solrParams.getParams("must_not"), BooleanClause.Occur.MUST_NOT);
|
||||
addQueries(clauses, solrParams.getParams("filter"), BooleanClause.Occur.FILTER);
|
||||
addQueries(clauses, solrParams.getParams("should"), BooleanClause.Occur.SHOULD);
|
||||
return clauses;
|
||||
}
|
||||
|
||||
private void addQueries(Map<QParser, BooleanClause.Occur> clausesDest, String[] subQueries, BooleanClause.Occur occur) throws SyntaxError {
|
||||
if (subQueries != null) {
|
||||
for (String subQuery : subQueries) {
|
||||
final QParser subParser = subQuery(subQuery, null);
|
||||
Query extQuery;
|
||||
if (BooleanClause.Occur.FILTER.equals(occur)) {
|
||||
extQuery = subParser.getQuery();
|
||||
if (!(extQuery instanceof ExtendedQuery) || (
|
||||
((ExtendedQuery) extQuery).getCache())) {
|
||||
extQuery = new FilterQuery(extQuery);
|
||||
}
|
||||
} else {
|
||||
extQuery = subParser.parse();
|
||||
}
|
||||
builder.add(extQuery, occur);
|
||||
clausesDest.put(subParser, occur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,18 +17,13 @@
|
|||
|
||||
package org.apache.solr.search.join;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.lucene.search.BooleanClause;
|
||||
import org.apache.lucene.search.BooleanClause.Occur;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.common.util.StrUtils;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
|
@ -42,22 +37,31 @@ public class FiltersQParser extends QParser {
|
|||
return "param";
|
||||
}
|
||||
|
||||
FiltersQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
|
||||
protected FiltersQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
|
||||
super(qstr, localParams, params, req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Query parse() throws SyntaxError {
|
||||
Map<Query,Occur> clauses = clauses();
|
||||
|
||||
exclude(clauses);
|
||||
|
||||
int numClauses = 0;
|
||||
public Query parse() throws SyntaxError {
|
||||
BooleanQuery query = parseImpl();
|
||||
return !query.clauses().isEmpty() ? wrapSubordinateClause(query) : noClausesQuery();
|
||||
}
|
||||
|
||||
protected BooleanQuery parseImpl() throws SyntaxError {
|
||||
Map<QParser, Occur> clauses = clauses();
|
||||
|
||||
exclude(clauses.keySet());
|
||||
|
||||
BooleanQuery.Builder builder = new BooleanQuery.Builder();
|
||||
numClauses += addQuery(builder, clauses);
|
||||
numClauses += addFilters(builder, clauses);
|
||||
// what about empty query?
|
||||
return numClauses > 0 ? wrapSubordinateClause(builder.build()) : noClausesQuery();
|
||||
for (Map.Entry<QParser, Occur> clause: clauses.entrySet()) {
|
||||
builder.add(unwrapQuery(clause.getKey().getQuery(), clause.getValue()), clause.getValue());
|
||||
}
|
||||
// what about empty query?
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
protected Query unwrapQuery(Query query, BooleanClause.Occur occur) {
|
||||
return query;
|
||||
}
|
||||
|
||||
protected Query wrapSubordinateClause(Query subordinate) throws SyntaxError {
|
||||
|
@ -68,30 +72,7 @@ public class FiltersQParser extends QParser {
|
|||
return new MatchAllDocsQuery();
|
||||
}
|
||||
|
||||
protected int addQuery(BooleanQuery.Builder builder, Map<Query,Occur> clauses) {
|
||||
int cnt=0;
|
||||
for (Map.Entry<Query, Occur> clause: clauses.entrySet()) {
|
||||
if (clause.getValue() == Occur.MUST) {
|
||||
builder.add(clause.getKey(), clause.getValue());
|
||||
cnt++;// shouldn't count more than once
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/** @return number of added clauses */
|
||||
protected int addFilters(BooleanQuery.Builder builder, Map<Query,Occur> clauses) throws SyntaxError {
|
||||
int count=0;
|
||||
for (Map.Entry<Query, Occur> clause: clauses.entrySet()) {
|
||||
if (clause.getValue() == Occur.FILTER) {
|
||||
builder.add( clause.getKey(), Occur.FILTER);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
protected void exclude(Map<Query,Occur> clauses) {
|
||||
protected void exclude(Collection<QParser> clauses) {
|
||||
Set<String> tagsToExclude = new HashSet<>();
|
||||
String excludeTags = localParams.get("excludeTags");
|
||||
if (excludeTags != null) {
|
||||
|
@ -99,18 +80,22 @@ public class FiltersQParser extends QParser {
|
|||
}
|
||||
@SuppressWarnings("rawtypes")
|
||||
Map tagMap = (Map) req.getContext().get("tags");
|
||||
final Collection<QParser> excludeSet;
|
||||
if (tagMap != null && !tagMap.isEmpty() && !tagsToExclude.isEmpty()) {
|
||||
clauses.keySet().removeAll(excludeSet(tagMap, tagsToExclude));
|
||||
} // else no filters were tagged
|
||||
excludeSet = excludeSet(tagMap, tagsToExclude);
|
||||
} else {
|
||||
excludeSet = Collections.emptySet();
|
||||
}
|
||||
clauses.removeAll(excludeSet);
|
||||
}
|
||||
|
||||
protected Map<Query,Occur> clauses() throws SyntaxError {
|
||||
protected Map<QParser,Occur> clauses() throws SyntaxError {
|
||||
String[] params = localParams.getParams(getFiltersParamName());
|
||||
if(params!=null && params.length == 0) { // never happens
|
||||
throw new SyntaxError("Local parameter "+getFiltersParamName() +
|
||||
" is not defined for "+stringIncludingLocalParams);
|
||||
}
|
||||
Map<Query,Occur> clauses = new IdentityHashMap<>();
|
||||
Map<QParser,Occur> clauses = new IdentityHashMap<>();
|
||||
|
||||
for (String filter : params==null ? new String[0] : params) {
|
||||
if(filter==null || filter.length() == 0) {
|
||||
|
@ -119,21 +104,20 @@ public class FiltersQParser extends QParser {
|
|||
}
|
||||
// as a side effect, qparser is mapped by tags in req context
|
||||
QParser parser = subQuery(filter, null);
|
||||
Query query = parser.getQuery();
|
||||
clauses.put(query, BooleanClause.Occur.FILTER);
|
||||
clauses.put(parser, BooleanClause.Occur.FILTER);
|
||||
}
|
||||
String queryText = localParams.get(QueryParsing.V);
|
||||
if (queryText != null && queryText.length() > 0) {
|
||||
QParser parser = subQuery(queryText, null);
|
||||
clauses.put(parser.getQuery(), BooleanClause.Occur.MUST);
|
||||
clauses.put(parser, BooleanClause.Occur.MUST);
|
||||
}
|
||||
return clauses;
|
||||
}
|
||||
|
||||
private Collection<?> excludeSet(@SuppressWarnings("rawtypes")
|
||||
private Collection<QParser> excludeSet(@SuppressWarnings("rawtypes")
|
||||
Map tagMap, Set<String> tagsToExclude) {
|
||||
|
||||
IdentityHashMap<Query,Boolean> excludeSet = new IdentityHashMap<>();
|
||||
IdentityHashMap<QParser,Boolean> excludeSet = new IdentityHashMap<>();
|
||||
for (String excludeTag : tagsToExclude) {
|
||||
Object olst = tagMap.get(excludeTag);
|
||||
// tagMap has entries of List<String,List<QParser>>, but subject to change in the future
|
||||
|
@ -141,12 +125,7 @@ public class FiltersQParser extends QParser {
|
|||
for (Object o : (Collection<?>)olst) {
|
||||
if (!(o instanceof QParser)) continue;
|
||||
QParser qp = (QParser)o;
|
||||
try {
|
||||
excludeSet.put(qp.getQuery(), Boolean.TRUE);
|
||||
} catch (SyntaxError syntaxError) {
|
||||
// This should not happen since we should only be retrieving a previously parsed query
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, syntaxError);
|
||||
}
|
||||
excludeSet.put(qp, Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
return excludeSet.keySet();
|
||||
|
|
|
@ -16,9 +16,21 @@
|
|||
*/
|
||||
package org.apache.solr.search.facet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.solr.SolrTestCaseHS;
|
||||
import org.apache.solr.client.solrj.SolrClient;
|
||||
import org.apache.solr.client.solrj.SolrServerException;
|
||||
import org.apache.solr.client.solrj.impl.InputStreamResponseParser;
|
||||
import org.apache.solr.client.solrj.impl.NoOpResponseParser;
|
||||
import org.apache.solr.client.solrj.request.json.DirectJsonQueryRequest;
|
||||
import org.apache.solr.client.solrj.request.json.JsonQueryRequest;
|
||||
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||
import org.apache.solr.client.solrj.response.SolrResponseBase;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -32,6 +44,10 @@ public class TestJsonFacetsWithNestedObjects extends SolrTestCaseHS{
|
|||
|
||||
private static void indexBooksAndReviews() throws Exception {
|
||||
final Client client = Client.localClient();
|
||||
indexDocs(client);
|
||||
}
|
||||
|
||||
private static void indexDocs(final Client client) throws IOException, SolrServerException, Exception {
|
||||
client.deleteByQuery("*:*", null);
|
||||
|
||||
SolrInputDocument book1 = sdoc(
|
||||
|
@ -337,6 +353,57 @@ public class TestJsonFacetsWithNestedObjects extends SolrTestCaseHS{
|
|||
);
|
||||
}
|
||||
|
||||
public void testBoolExclusion() throws Exception {
|
||||
final Client client = Client.localClient();
|
||||
ModifiableSolrParams p = params("rows", "0");
|
||||
client.testJQ(params(p,
|
||||
"json.queries", "{'childquery':'comment_t:*'," +
|
||||
"'parentquery':'type_s:book'," +
|
||||
"'child.fq':[ {'#author':{'field':{'f':'author_s','query':'dan'}}}," +
|
||||
"{'#stars':{'field':{'f':'stars_i','query':'4'}}}]," +
|
||||
"'snowcrash':{'field':{'f':'title_t','query':'Snow Crash'}}" +
|
||||
"}",
|
||||
"json.query", "{'#top':{'parent':{'which':{'param':'parentquery'}," +
|
||||
"'query':{'bool':{'filter':{'param':'child.fq'}," +
|
||||
"'must':{'param':'childquery'}" +
|
||||
"}}}}}",
|
||||
"json.facet", "{" +
|
||||
"'comments_for_author':{'domain':{" +
|
||||
"'excludeTags':'top', " +
|
||||
"'blockChildren':'{!v=$parentquery}'," +
|
||||
"'filter':'{!bool filter=$child.fq filter=$childquery excludeTags=author}'" +
|
||||
"}," +
|
||||
"'type':'terms', 'field':'author_s'" +
|
||||
"}" +
|
||||
" ,comments_for_stars: {" +
|
||||
" domain: { excludeTags:top, " +
|
||||
"'blockChildren':'{!v=$parentquery}'," +
|
||||
" filter:\"{!bool filter=$child.fq excludeTags=stars filter=$childquery}\" }," +
|
||||
"type:terms, field:stars_i" +
|
||||
"}" +
|
||||
",comments_for_stars_parent_filter: {" +
|
||||
"domain: { " +
|
||||
"excludeTags:top, " +
|
||||
"'blockChildren':'{!v=$parentquery}'," +
|
||||
"filter:[\"{!bool filter=$child.fq excludeTags=stars filter=$childquery}\","
|
||||
+ "\"{!child of=$parentquery}{!bool filter=$snowcrash}}\"] }," +
|
||||
"type:terms, field:stars_i"+
|
||||
" }" +
|
||||
"}",
|
||||
"json.fields", "'id,title_t'")
|
||||
, "response=={numFound:0,start:0,'numFoundExact':true,docs:[]}"
|
||||
, "facets=={ count:0," +
|
||||
"comments_for_author:{" +
|
||||
" buckets:[ {val:mary, count:1} ]}" +
|
||||
"," +
|
||||
"comments_for_stars:{" +
|
||||
" buckets:[ {val:2, count:1}," +
|
||||
" {val:3, count:1} ]}," +
|
||||
"comments_for_stars_parent_filter:{" +
|
||||
" buckets:[ {val:2, count:1} ]}" +
|
||||
"}");
|
||||
}
|
||||
|
||||
public void testUniqueBlock() throws Exception {
|
||||
final Client client = Client.localClient();
|
||||
ModifiableSolrParams p = params("rows","0");
|
||||
|
|
Loading…
Reference in New Issue