SOLR-14539: Introducing {!bool excludeTags=foo,bar}

This commit is contained in:
Mikhail Khludnev 2020-06-28 16:29:24 +03:00
parent 20c1fdbf7e
commit f647400e31
4 changed files with 138 additions and 76 deletions

View File

@ -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)

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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");