SOLR-12490: Introducing json.queries to define many named queries in Query DSL.

This commit is contained in:
Mikhail Khludnev 2020-01-03 23:22:21 +03:00
parent 479e736469
commit 8fba8eba13
3 changed files with 73 additions and 35 deletions

View File

@ -153,12 +153,14 @@ Upgrade Notes
If you prefer to keep the old (but insecure) serialization strategy, you can start your nodes using the
property: `-Dsolr.useUnsafeOverseerResponse=true`. Keep in mind that this will be removed in future version of Solr.
* SOLR-13808: add cache=false into uderneath BoolQParser's filter clause or {"bool":{"filter":..}} to avoid caching in
* SOLR-13808: add cache=false into underneath BoolQParser's filter clause or {"bool":{"filter":..}} to avoid caching in
filterCache. (Mikhail Khludnev)
New Features
---------------------
(No changes)
* SOLR-12490: Introducing json.queries in JSON Request API. Every property of this object holds one or many named
Query DSL queries. It's optional and doesn't impact response without explicit referencing these queries by names
(Anatolii Siuniaev via Mikhail Khludnev)
Improvements
---------------------

View File

@ -192,6 +192,7 @@ public class RequestUtil {
// implement compat for existing components...
JsonQueryConverter jsonQueryConverter = new JsonQueryConverter();
if (json != null && !isShard) {
for (Map.Entry<String,Object> entry : json.entrySet()) {
String key = entry.getKey();
@ -214,48 +215,62 @@ public class RequestUtil {
out = "rows";
} else if (SORT.equals(key)) {
out = SORT;
} else if ("queries".equals(key)) {
Object queriesJsonObj = entry.getValue();
if (queriesJsonObj instanceof Map && queriesJsonObj != null) {
@SuppressWarnings("unchecked")
final Map<String,Object> queriesAsMap = (Map<String,Object>) queriesJsonObj;
for (Map.Entry<String,Object> queryJsonProperty : queriesAsMap.entrySet()) {
out = queryJsonProperty.getKey();
arr = true;
isQuery = true;
convertJsonPropertyToLocalParams(newMap, jsonQueryConverter, queryJsonProperty, out, isQuery, arr);
}
continue;
} else {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Expected Map for 'queries', received " + queriesJsonObj);
}
} else if ("params".equals(key) || "facet".equals(key) ) {
// handled elsewhere
continue;
} else {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown top-level key in JSON request : " + key);
}
Object val = entry.getValue();
if (arr) {
String[] existing = newMap.get(out);
List lst = val instanceof List ? (List)val : null;
int existingSize = existing==null ? 0 : existing.length;
int jsonSize = lst==null ? 1 : lst.size();
String[] newval = new String[ existingSize + jsonSize ];
for (int i=0; i<existingSize; i++) {
newval[i] = existing[i];
}
if (lst != null) {
for (int i = 0; i < jsonSize; i++) {
Object v = lst.get(i);
newval[existingSize + i] = isQuery ? jsonQueryConverter.toLocalParams(v, newMap) : v.toString();
}
} else {
newval[newval.length-1] = isQuery ? jsonQueryConverter.toLocalParams(val, newMap) : val.toString();
}
newMap.put(out, newval);
} else {
newMap.put(out, new String[]{isQuery ? jsonQueryConverter.toLocalParams(val, newMap) : val.toString()});
}
convertJsonPropertyToLocalParams(newMap, jsonQueryConverter, entry, out, isQuery, arr);
}
}
if (json != null) {
req.setJSON(json);
}
}
private static void convertJsonPropertyToLocalParams(Map<String, String[]> outMap, JsonQueryConverter jsonQueryConverter, Map.Entry<String, Object> jsonProperty, String outKey, boolean isQuery, boolean arr) {
Object val = jsonProperty.getValue();
if (arr) {
String[] existing = outMap.get(outKey);
List<?> lst = val instanceof List ? (List<?>)val : null;
int existingSize = existing==null ? 0 : existing.length;
int jsonSize = lst==null ? 1 : lst.size();
String[] newval = new String[ existingSize + jsonSize ];
for (int i=0; i<existingSize; i++) {
newval[i] = existing[i];
}
if (lst != null) {
for (int i = 0; i < jsonSize; i++) {
Object v = lst.get(i);
newval[existingSize + i] = isQuery ? jsonQueryConverter.toLocalParams(v, outMap) : v.toString();
}
} else {
newval[newval.length-1] = isQuery ? jsonQueryConverter.toLocalParams(val, outMap) : val.toString();
}
outMap.put(outKey, newval);
} else {
outMap.put(outKey, new String[]{isQuery ? jsonQueryConverter.toLocalParams(val, outMap) : val.toString()});
}
}
// queryParamName is something like json.facet or json.query, or just json...
@ -295,6 +310,7 @@ public class RequestUtil {
Object o = ObjectBuilder.getVal(parser);
if (!(o instanceof Map)) return;
@SuppressWarnings("unchecked")
Map<String,Object> map = (Map<String,Object>)o;
// To make consistent with json.param handling, we should make query params come after json params (i.e. query params should
// appear to overwrite json params.
@ -310,7 +326,7 @@ public class RequestUtil {
if (val == null) {
params.remove(key);
} else if (val instanceof List) {
List lst = (List) val;
List<?> lst = (List<?>) val;
String[] vals = new String[lst.size()];
for (int i = 0; i < vals.length; i++) {
vals[i] = lst.get(i).toString();

View File

@ -56,6 +56,7 @@ public class TestJsonFacets extends SolrTestCaseHS {
private static int origTableSize;
private static FacetField.FacetMethod origDefaultFacetMethod;
@SuppressWarnings("deprecation")
@BeforeClass
public static void beforeTests() throws Exception {
systemSetPropertySolrDisableShardsWhitelist("true");
@ -83,6 +84,7 @@ public class TestJsonFacets extends SolrTestCaseHS {
}
}
@SuppressWarnings("deprecation")
@AfterClass
public static void afterTests() throws Exception {
systemClearPropertySolrDisableShardsWhitelist();
@ -1063,7 +1065,6 @@ public class TestJsonFacets extends SolrTestCaseHS {
}
public static void doStatsTemplated(Client client, ModifiableSolrParams p) throws Exception {
int numShards = client.local() ? 1 : client.getClientProvider().all().size();
p.set("Z_num_i", "Z_" + p.get("num_i") );
p.set("Z_num_l", "Z_" + p.get("num_l") );
p.set("sparse_num_d", "sparse_" + p.get("num_d") );
@ -2290,6 +2291,19 @@ public class TestJsonFacets extends SolrTestCaseHS {
"}"
);
//test filter using queries from json.queries
client.testJQ(params(p, "q", "*:*"
, "json.queries", "{catS:{'#cat_sA': '${cat_s}:A'}, ff:[{'#id_1':'-id:1'},{'#id_2':'-id:2'}]}"
, "json.facet", "{" +
",t_filt1:{${terms} type:terms, field:${cat_s}, domain:{filter:{param:catS} } }" + // test filter via "param" type from .queries
",t_filt2:{${terms} type:terms, field:${cat_s}, domain:{filter:{param:ff}} }" + // test multi-valued query parameter from .queries
"}"
)
, "facets=={ count:6, " +
",t_filt1:{ buckets:[ {val:A, count:2}] } " +
",t_filt2:{ buckets:[ {val:B, count:2}, {val:A, count:1}] } " +
"}"
);
// test acc reuse (i.e. reset() method). This is normally used for stats that are not calculated in the first phase,
// currently non-sorting stats.
@ -2907,7 +2921,7 @@ public class TestJsonFacets extends SolrTestCaseHS {
int commitPercent = 10;
int ndocs=1000;
Map<Integer, Map<Integer, List<Integer>>> model = new HashMap(); // cat->where->list<ids>
Map<Integer, Map<Integer, List<Integer>>> model = new HashMap<>(); // cat->where->list<ids>
for (int i=0; i<ndocs; i++) {
Integer cat = r.nextInt(numCat);
Integer where = r.nextInt(numWhere);
@ -3328,7 +3342,6 @@ public class TestJsonFacets extends SolrTestCaseHS {
}
public void doTestErrors(Client client) throws Exception {
ModifiableSolrParams p = params("rows", "0");
client.deleteByQuery("*:*", null);
try {
@ -3646,11 +3659,18 @@ public class TestJsonFacets extends SolrTestCaseHS {
req("q", "*:*", "rows", "0", "json.facet", "{cat_s:{type:terms,field:cat_s,sort:[\"count desc\"]}}"),
SolrException.ErrorCode.BAD_REQUEST);
assertQEx("Should fail as facet is not of type map",
"Expected Map for 'facet', received ArrayList=[{}]",
req("q", "*:*", "rows", "0", "json.facet", "[{}]"), SolrException.ErrorCode.BAD_REQUEST);
assertQEx("Should fail as queries is not of type map",
"Expected Map for 'queries', received [{}]",
req("q", "*:*", "rows", "0", "json.queries", "[{}]"), SolrException.ErrorCode.BAD_REQUEST);
assertQEx("Should fail as queries are null in JSON",
"Expected Map for 'queries', received null",
req("json", "{query:\"*:*\", queries:null}"), SolrException.ErrorCode.BAD_REQUEST);
// range facets
assertQEx("Should fail as 'other' is of type Map",
"Expected list of string or comma separated string values for 'other', " +