You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by mk...@apache.org on 2020/06/30 15:55:55 UTC

[lucene-solr] branch master updated: SOLR-14539: Introducing {!bool excludeTags=foo, bar}

This is an automated email from the ASF dual-hosted git repository.

mkhl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/master by this push:
     new f647400  SOLR-14539: Introducing {!bool excludeTags=foo,bar}
f647400 is described below

commit f647400e31c0fb238744cece8d3943bc15253ec7
Author: Mikhail Khludnev <mk...@apache.org>
AuthorDate: Sun Jun 28 16:29:24 2020 +0300

    SOLR-14539: Introducing {!bool excludeTags=foo,bar}
---
 solr/CHANGES.txt                                   |  2 +
 .../org/apache/solr/search/BoolQParserPlugin.java  | 52 ++++++++-----
 .../apache/solr/search/join/FiltersQParser.java    | 91 +++++++++-------------
 .../facet/TestJsonFacetsWithNestedObjects.java     | 67 ++++++++++++++++
 4 files changed, 137 insertions(+), 75 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 9553d29..1c14602 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -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)
diff --git a/solr/core/src/java/org/apache/solr/search/BoolQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/BoolQParserPlugin.java
index 04a9d5a..e3e030b 100644
--- a/solr/core/src/java/org/apache/solr/search/BoolQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/BoolQParserPlugin.java
@@ -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();
+        return parseImpl();
+      }
+
+      @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(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();
+        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(BooleanQuery.Builder builder, String[] subQueries, BooleanClause.Occur occur) throws SyntaxError {
+      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);
           }
         }
       }
diff --git a/solr/core/src/java/org/apache/solr/search/join/FiltersQParser.java b/solr/core/src/java/org/apache/solr/search/join/FiltersQParser.java
index a3557ff..851f48b 100644
--- a/solr/core/src/java/org/apache/solr/search/join/FiltersQParser.java
+++ b/solr/core/src/java/org/apache/solr/search/join/FiltersQParser.java
@@ -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();
diff --git a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetsWithNestedObjects.java b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetsWithNestedObjects.java
index 1e5b203..4cc943c 100644
--- a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetsWithNestedObjects.java
+++ b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetsWithNestedObjects.java
@@ -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");