You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2018/06/26 16:53:56 UTC
[22/26] lucene-solr:jira/solr-11665: SOLR-12398: Add Heatmap facet
option to JSON Facet API. * moved the preponderance of the implementation
from SpatialHeatmapFacets (used by SimpleFacets) into the new API.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/095f9eb9/solr/core/src/test/org/apache/solr/search/facet/SpatialHeatmapFacetsTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/facet/SpatialHeatmapFacetsTest.java b/solr/core/src/test/org/apache/solr/search/facet/SpatialHeatmapFacetsTest.java
new file mode 100644
index 0000000..5af7bb6
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/facet/SpatialHeatmapFacetsTest.java
@@ -0,0 +1,349 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.solr.search.facet;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.carrotsearch.randomizedtesting.annotations.Repeat;
+import org.apache.solr.BaseDistributedSearchTestCase;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.FacetParams;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Test Heatmap Facets (both impls) */
+public class SpatialHeatmapFacetsTest extends BaseDistributedSearchTestCase {
+ private static final String FIELD = "srpt_quad";
+
+ @BeforeClass
+ public static void beforeSuperClass() throws Exception {
+ schemaString = "schema-spatial.xml";
+ configString = "solrconfig-basic.xml";
+
+ //Strictly not necessary (set already in Ant & Maven) but your IDE might not have this set
+ System.setProperty("java.awt.headless", "true");
+ }
+
+ /** Tests SimpleFacets/Classic faceting implementation of heatmaps */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testClassicFacets() throws Exception { // AKA SimpleFacets
+ handle.clear();
+ handle.put("QTime", SKIPVAL);
+ handle.put("timestamp", SKIPVAL);
+ handle.put("maxScore", SKIPVAL);
+
+ SolrParams baseParams = params("q", "*:*", "rows", "0", "facet", "true", FacetParams.FACET_HEATMAP, FIELD);
+
+ final String testBox = "[\"50 50\" TO \"180 90\"]";//top-right somewhere on edge (whatever)
+
+ //----- First we test gridLevel derivation
+ try {
+ getHmObj(query(params(baseParams, FacetParams.FACET_HEATMAP_GEOM, testBox, FacetParams.FACET_HEATMAP_DIST_ERR, "0"))).get("gridLevel");
+ fail();
+ } catch (SolrException e) {
+ assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, e.code());
+ }
+ try {
+ getHmObj(query(params(baseParams, FacetParams.FACET_HEATMAP_GEOM, testBox, FacetParams.FACET_HEATMAP_DIST_ERR_PCT, "0"))).get("gridLevel");
+ fail();
+ } catch (SolrException e) {
+ assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, e.code());
+ }
+ // Monkeying with these params changes the gridLevel in different directions. We don't test the exact
+ // computation here; that's not _that_ relevant, and is Lucene spatial's job (not Solr) any way.
+ assertEquals(7, getHmObj(query(params(baseParams, FacetParams.FACET_HEATMAP_GEOM, testBox))).get("gridLevel"));//default
+ assertEquals(3, getHmObj(query(params(baseParams, FacetParams.FACET_HEATMAP_GEOM, testBox, FacetParams.FACET_HEATMAP_LEVEL, "3"))).get("gridLevel"));
+ assertEquals(2, getHmObj(query(params(baseParams, FacetParams.FACET_HEATMAP_GEOM, testBox, FacetParams.FACET_HEATMAP_DIST_ERR, "100"))).get("gridLevel"));
+ //TODO test impact of distance units
+ assertEquals(9, getHmObj(query(params(baseParams, FacetParams.FACET_HEATMAP_GEOM, testBox, FacetParams.FACET_HEATMAP_DIST_ERR_PCT, "0.05"))).get("gridLevel"));
+ assertEquals(6, getHmObj(query(params(baseParams, FacetParams.FACET_HEATMAP_DIST_ERR_PCT, "0.10"))).get("gridLevel"));
+
+ //test key output label doing 2 heatmaps with different settings on the same field
+ {
+ final ModifiableSolrParams params = params(baseParams, FacetParams.FACET_HEATMAP_DIST_ERR_PCT, "0.10");
+ String courseFormat = random().nextBoolean() ? "png" : "ints2D";
+ params.add(FacetParams.FACET_HEATMAP, "{!key=course "
+ + FacetParams.FACET_HEATMAP_LEVEL + "=2 "
+ + FacetParams.FACET_HEATMAP_FORMAT + "=" + courseFormat
+ + "}" + FIELD);
+ final QueryResponse response = query(params);
+ assertEquals(6, getHmObj(response).get("gridLevel"));//same test as above
+ assertEquals(2, response.getResponse().findRecursive("facet_counts", "facet_heatmaps", "course", "gridLevel"));
+ assertTrue(((NamedList<Object>) response.getResponse().findRecursive("facet_counts", "facet_heatmaps", "course"))
+ .asMap(0).containsKey("counts_" + courseFormat));
+ }
+
+ // ------ Index data
+
+ index("id", "0", FIELD, "ENVELOPE(100, 120, 80, 40)");// on right side
+ index("id", "1", FIELD, "ENVELOPE(-120, -110, 80, 20)");// on left side (outside heatmap)
+ index("id", "3", FIELD, "POINT(70 60)");//just left of BOX 0
+ index("id", "4", FIELD, "POINT(91 89)");//just outside box 0 (above it) near pole,
+
+ commit();
+
+ // ----- Search
+ // this test simply has some 0's, nulls, 1's and a 2 in there.
+ NamedList hmObj = getHmObj(query(params(baseParams,
+ FacetParams.FACET_HEATMAP_GEOM, "[\"50 20\" TO \"180 90\"]",
+ FacetParams.FACET_HEATMAP_LEVEL, "4")));
+ List<List<Integer>> counts = (List<List<Integer>>) hmObj.get("counts_ints2D");
+ assertEquals(
+ Arrays.asList(
+ Arrays.asList(0, 0, 2, 1, 0, 0),
+ Arrays.asList(0, 0, 1, 1, 0, 0),
+ Arrays.asList(0, 1, 1, 1, 0, 0),
+ Arrays.asList(0, 0, 1, 1, 0, 0),
+ Arrays.asList(0, 0, 1, 1, 0, 0),
+ null,
+ null
+ ),
+ counts
+ );
+
+ // now this time we add a filter query and exclude it
+ QueryResponse response = query(params(baseParams,
+ "fq", "{!tag=excludeme}id:0", // filter to only be id:0
+ FacetParams.FACET_HEATMAP, "{!ex=excludeme}" + FIELD, // exclude the filter
+ FacetParams.FACET_HEATMAP_GEOM, "[\"50 20\" TO \"180 90\"]",
+ FacetParams.FACET_HEATMAP_LEVEL, "4"));
+ assertEquals(1, response.getResults().getNumFound());// because of our 'fq'
+ hmObj = getHmObj(response);
+ counts = (List<List<Integer>>) hmObj.get("counts_ints2D");
+ assertEquals(
+ Arrays.asList( // same counts as before
+ Arrays.asList(0, 0, 2, 1, 0, 0),
+ Arrays.asList(0, 0, 1, 1, 0, 0),
+ Arrays.asList(0, 1, 1, 1, 0, 0),
+ Arrays.asList(0, 0, 1, 1, 0, 0),
+ Arrays.asList(0, 0, 1, 1, 0, 0),
+ null,
+ null
+ ),
+ counts
+ );
+
+ // test using a circle input shape
+ hmObj = getHmObj(query(params(baseParams,
+ FacetParams.FACET_HEATMAP_GEOM, "BUFFER(POINT(110 40), 7)",
+ FacetParams.FACET_HEATMAP_LEVEL, "7")));
+ counts = (List<List<Integer>>) hmObj.get("counts_ints2D");
+ assertEquals(
+ Arrays.asList(
+ Arrays.asList(0, 1, 1, 1, 1, 1, 1, 0),//curved; we have a 0
+ Arrays.asList(0, 1, 1, 1, 1, 1, 1, 0),//curved; we have a 0
+ Arrays.asList(0, 1, 1, 1, 1, 1, 1, 0),//curved; we have a 0
+ Arrays.asList(1, 1, 1, 1, 1, 1, 1, 1),
+ Arrays.asList(1, 1, 1, 1, 1, 1, 1, 1),
+ Arrays.asList(1, 1, 1, 1, 1, 1, 1, 1),
+ null, null, null, null, null//no data here (below edge of rect 0)
+ ),
+ counts
+ );
+
+ // Search in no-where ville and get null counts
+ assertNull(getHmObj(query(params(baseParams, FacetParams.FACET_HEATMAP_GEOM, "ENVELOPE(0, 10, -80, -90)"))).get("counts_ints2D"));
+
+ Object v = getHmObj(query(params(baseParams, FacetParams.FACET_HEATMAP_FORMAT, "png"))).get("counts_png");
+ assertTrue(v instanceof byte[]);
+ //simply test we can read the image
+ assertNotNull(FacetHeatmap.PngHelper.readImage((byte[]) v));
+ //good enough for this test method
+ }
+
+ private ModifiableSolrParams params(SolrParams baseParams, String... moreParams) {
+ final ModifiableSolrParams params = new ModifiableSolrParams(baseParams);
+ params.add(params(moreParams));//actually replaces
+ return params;
+ }
+
+ /** Tests JSON Facet module implementation of heatmaps. */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testJsonFacets() throws Exception {
+ /*
+ THIS IS THE MOSTLY SAME CODE as above with tweaks to request it using the JSON Facet approach.
+ Near-duplication is sad; not clear if one test doing both is better -- would be awkward
+ */
+ handle.clear();
+ handle.put("QTime", SKIPVAL);
+ handle.put("timestamp", SKIPVAL);
+ handle.put("maxScore", SKIPVAL);
+
+ SolrParams baseParams = params("q", "*:*", "rows", "0");
+
+ final String testBox = "[\"50 50\" TO \"180 90\"]";//top-right somewhere on edge (whatever)
+
+ // ------ Index data
+
+ index("id", "0", FIELD, "ENVELOPE(100, 120, 80, 40)");// on right side
+ index("id", "1", FIELD, "ENVELOPE(-120, -110, 80, 20)");// on left side (outside heatmap)
+ index("id", "3", FIELD, "POINT(70 60)");//just left of BOX 0
+ index("id", "4", FIELD, "POINT(91 89)");//just outside box 0 (above it) near pole,
+
+ commit();
+
+ //----- Test gridLevel derivation
+ try {
+ query(params(baseParams, "json.facet", "{f1:{type:heatmap, f:" + FIELD + ", geom:'" + testBox + "', distErr:0}}"));
+ fail();
+ } catch (SolrException e) {
+ assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, e.code());
+ }
+ try {
+ query(params(baseParams, "json.facet", "{f1:{type:heatmap, f:" + FIELD + ", geom:'" + testBox + "', distErrPct:0}}"));
+ fail();
+ } catch (SolrException e) {
+ assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, e.code());
+ }
+ // Monkeying with these params changes the gridLevel in different directions. We don't test the exact
+ // computation here; that's not _that_ relevant, and is Lucene spatial's job (not Solr) any way.
+ assertEquals(7, getHmObj(query(params(baseParams,
+ "json.facet", "{f1:{type:heatmap, f:" + FIELD + ", geom:'" + testBox + "'}}"))).get("gridLevel"));//default
+ assertEquals(3, getHmObj(query(params(baseParams,
+ "json.facet", "{f1:{type:heatmap, f:" + FIELD + ", geom:'" + testBox + "', gridLevel:3}}"))).get("gridLevel"));
+ assertEquals(2, getHmObj(query(params(baseParams,
+ "json.facet", "{f1:{type:heatmap, f:" + FIELD + ", geom:'" + testBox + "', distErr:100}}"))).get("gridLevel"));
+ //TODO test impact of distance units
+ assertEquals(9, getHmObj(query(params(baseParams,
+ "json.facet", "{f1:{type:heatmap, f:" + FIELD + ", geom:'" + testBox + "', distErrPct:0.05}}"))).get("gridLevel"));
+ assertEquals(6, getHmObj(query(params(baseParams,
+ "json.facet", "{f1:{type:heatmap, f:" + FIELD + ", distErrPct:0.10}}"))).get("gridLevel"));
+
+ // ----- Search
+ // this test simply has some 0's, nulls, 1's and a 2 in there.
+ NamedList hmObj = getHmObj(query(params(baseParams,
+ "json.facet", "{f1:{type:heatmap, f:" + FIELD + " geom:'[\"50 20\" TO \"180 90\"]', gridLevel:4}}")));
+ List<List<Integer>> counts = (List<List<Integer>>) hmObj.get("counts_ints2D");
+ List<List<Integer>> expectedCounts1 = Arrays.asList(
+ Arrays.asList(0, 0, 2, 1, 0, 0),
+ Arrays.asList(0, 0, 1, 1, 0, 0),
+ Arrays.asList(0, 1, 1, 1, 0, 0),
+ Arrays.asList(0, 0, 1, 1, 0, 0),
+ Arrays.asList(0, 0, 1, 1, 0, 0),
+ null,
+ null
+ );
+ assertEquals( expectedCounts1, counts);
+
+ // now this time we add a filter query and exclude it
+ QueryResponse response = query(params(baseParams,
+ "fq", "{!tag=excludeme}id:0", // filter to only be id:0
+ "json.facet", "{f1:{type:heatmap, excludeTags:['excludeme'], f:" + FIELD + ", geom:'[\"50 20\" TO \"180 90\"]', gridLevel:4}}"));
+
+ assertEquals(1, response.getResults().getNumFound());// because of our 'fq'
+ hmObj = getHmObj(response);
+ counts = (List<List<Integer>>) hmObj.get("counts_ints2D");
+ assertEquals( expectedCounts1, counts);
+
+ {
+ // impractical example but nonetheless encloses the points of both doc3 and doc4 (both of which are points)
+ final String jsonHeatmap = "facet:{hm:{type:heatmap, f:" + FIELD + ", geom:'MULTIPOINT(70 60, 91 89)', distErrPct:0.2}}";
+ response = query(params(baseParams,
+ "json.facet", "{" +
+ "q1:{type:query, q:'id:3', " + jsonHeatmap + " }, " +
+ "q2:{type:query, q:'id:4', " + jsonHeatmap + " } " +
+ "}"));
+ {
+ final NamedList q1Res = (NamedList) response.getResponse().findRecursive("facets", "q1");
+ assertEquals("1", q1Res.get("count").toString());
+ final NamedList q2Res = (NamedList) response.getResponse().findRecursive("facets", "q2");
+ assertEquals("1", q2Res.get("count").toString());
+ // essentially, these will differ only in the heatmap counts but otherwise will be the same
+ assertNotNull(compare(q1Res, q2Res, flags, handle));
+ }
+ }
+
+ // test using a circle input shape
+ hmObj = getHmObj(query(params(baseParams,
+ "json.facet", "{f1:{type:heatmap, f:" + FIELD + ", geom:'BUFFER(POINT(110 40), 7)', gridLevel:7}}")));
+ counts = (List<List<Integer>>) hmObj.get("counts_ints2D");
+ assertEquals(
+ Arrays.asList(
+ Arrays.asList(0, 1, 1, 1, 1, 1, 1, 0),//curved; we have a 0
+ Arrays.asList(0, 1, 1, 1, 1, 1, 1, 0),//curved; we have a 0
+ Arrays.asList(0, 1, 1, 1, 1, 1, 1, 0),//curved; we have a 0
+ Arrays.asList(1, 1, 1, 1, 1, 1, 1, 1),
+ Arrays.asList(1, 1, 1, 1, 1, 1, 1, 1),
+ Arrays.asList(1, 1, 1, 1, 1, 1, 1, 1),
+ null, null, null, null, null//no data here (below edge of rect 0)
+ ),
+ counts
+ );
+
+ // Search in no-where ville and get null counts
+ assertNull(getHmObj(query(params(baseParams,
+ "json.facet", "{f1:{type:heatmap, f:" + FIELD + ", geom:'ENVELOPE(0, 10, -80, -90)'}}"))).get("counts_ints2D"));
+
+ Object v = getHmObj(query(params(baseParams,
+ "json.facet", "{f1:{type:heatmap, f:" + FIELD + ", format:png }}"))).get("counts_png");
+ assertTrue(v instanceof byte[]);
+ //simply test we can read the image
+ assertNotNull(FacetHeatmap.PngHelper.readImage((byte[]) v));
+ //good enough for this test method
+ }
+
+ private NamedList getHmObj(QueryResponse response) {
+ // classic faceting
+ final NamedList classicResp = (NamedList) response.getResponse().findRecursive("facet_counts", "facet_heatmaps", FIELD);
+ if (classicResp != null) {
+ return classicResp;
+ }
+ // JSON Facet
+ return (NamedList) response.getResponse().findRecursive("facets", "f1");
+ }
+
+ @Test
+ @Repeat(iterations = 3)
+ public void testPng() {
+ //We test via round-trip randomized data:
+
+ // Make random data
+ int columns = random().nextInt(100) + 1;
+ int rows = random().nextInt(100) + 1;
+ int[] counts = new int[columns * rows];
+ for (int i = 0; i < counts.length; i++) {
+ final int ri = random().nextInt(10);
+ if (ri >= 0 && ri <= 3) {
+ counts[i] = ri; // 0 thru 3 will be made common
+ } else if (ri > 3) {
+ counts[i] = Math.abs(random().nextInt());//lots of other possible values up to max
+ }
+ }
+ // Round-trip
+ final byte[] bytes = FacetHeatmap.asPngBytes(columns, rows, counts, null);
+ int[] countsOut = random().nextBoolean() ? new int[columns * rows] : null;
+ int base = 0;
+ if (countsOut != null) {
+ base = 9;
+ Arrays.fill(countsOut, base);
+ }
+ countsOut = FacetHeatmap.addPngToIntArray(bytes, countsOut);
+ // Test equal
+ assertEquals(counts.length, countsOut.length);
+ for (int i = 0; i < countsOut.length; i++) {
+ assertEquals(counts[i], countsOut[i] - base);//back out the base input to prove we added
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/095f9eb9/solr/solr-ref-guide/src/json-facet-api.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/json-facet-api.adoc b/solr/solr-ref-guide/src/json-facet-api.adoc
index a7766b2..21287a0 100644
--- a/solr/solr-ref-guide/src/json-facet-api.adoc
+++ b/solr/solr-ref-guide/src/json-facet-api.adoc
@@ -300,6 +300,52 @@ By default, the ranges used to compute range faceting between `start` and `end`
|facet |Aggregations, metrics, or nested facets that will be calculated for every returned bucket
|===
+== Heatmap Facet
+
+The heatmap facet generates a 2D grid of facet counts for documents having spatial data in each grid cell.
+
+This feature is primarily documented in the <<spatial-search.adoc#heatmap-faceting,spatial>> section of the reference guide.
+The key parameters are `type` to specify `heatmap` and `field` to indicate a spatial RPT field.
+The rest of the parameter names use the same names and semantics mirroring
+ facet.heatmap query-parameter style faceting, albeit without the "facet.heatmap." prefix.
+For example `geom` here corresponds to `facet.heatmap.geom` in a facet.heatmap command.
+
+Like the other facet types, heatmaps may have a custom domain (e.g. to exclude filters) and they can be subordinate to
+other facet types like a query facet. However, unlike those, a heatmap facet cannot have facets or stats hang beneath --
+at least not yet.
+
+Here's an example query:
+[source,java]
+----
+{
+ hm : {
+ type : heatmap,
+ field : points_srpt,
+ geom : "[-49.492,-180 TO 64.701,73.125]",
+ distErrPct : 0.5
+ }
+}
+----
+
+And the facet response will look like:
+[source,json]
+----
+{
+"facets":{
+ "count":145725,
+ "hm":{
+ "gridLevel":1,
+ "columns":6,
+ "rows":4,
+ "minX":-180.0,
+ "maxX":90.0,
+ "minY":-90.0,
+ "maxY":90.0,
+ "counts_ints2D":[[68,1270,459,5359,39456,1713],[123,10472,13620,7777,18376,6239],[88,6,3898,989,1314,255],[0,0,30,1,0,1]]
+ }}}
+----
+
+
== Filtering Facets
One can filter the domain *before* faceting via the `filter` keyword in the `domain` block of the facet.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/095f9eb9/solr/solr-ref-guide/src/spatial-search.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/spatial-search.adoc b/solr/solr-ref-guide/src/spatial-search.adoc
index 3e4eaab..47d19c4 100644
--- a/solr/solr-ref-guide/src/spatial-search.adoc
+++ b/solr/solr-ref-guide/src/spatial-search.adoc
@@ -375,10 +375,13 @@ When using this field type, you will likely _not_ want to mark the field as stor
The RPT field supports generating a 2D grid of facet counts for documents having spatial data in each grid cell. For high-detail grids, this can be used to plot points, and for lesser detail it can be used for heatmap generation. The grid cells are determined at index-time based on RPT's configuration. At facet counting time, the indexed cells in the region of interest are traversed and a grid of counters corresponding to each cell are incremented. Solr can return the data in a straight-forward 2D array of integers or in a PNG which compresses better for larger data sets but must be decoded.
-The heatmap feature is accessed from Solr's faceting feature. As a part of faceting, it supports the `key` local parameter as well as excluding tagged filter queries, just like other types of faceting do. This allows multiple heatmaps to be returned on the same field with different filters.
+The heatmap feature is accessible both from Solr's standard faceting feature, plus the newer more flexible <<json-facet-api.adoc#heatmap-facet,JSON Facet API>>.
+We'll proceed now with standard faceting.
+As a part of faceting, it supports the `key` local parameter as well as excluding tagged filter queries, just like other types of faceting do.
+This allows multiple heatmaps to be returned on the same field with different filters.
`facet`::
-Set to `true` to enable faceting.
+Set to `true` to enable standard faceting.
`facet.heatmap`::
The field name of type RPT.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/095f9eb9/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java b/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
index 253ba40..4a67581 100644
--- a/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
+++ b/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
@@ -481,7 +481,9 @@ public abstract class BaseDistributedSearchTestCase extends SolrTestCaseJ4 {
*/
protected void indexDoc(SolrInputDocument doc) throws IOException, SolrServerException {
controlClient.add(doc);
-
+ if (shardCount == 0) {//mostly for temp debugging
+ return;
+ }
int which = (doc.getField(id).toString().hashCode() & 0x7fffffff) % clients.size();
SolrClient client = clients.get(which);
client.add(doc);
@@ -599,6 +601,10 @@ public abstract class BaseDistributedSearchTestCase extends SolrTestCaseJ4 {
final QueryResponse controlRsp = controlClient.query(params);
validateControlData(controlRsp);
+ if (shardCount == 0) {//mostly for temp debugging
+ return controlRsp;
+ }
+
params.remove("distrib");
if (setDistribParams) setDistributedParams(params);
@@ -872,6 +878,15 @@ public abstract class BaseDistributedSearchTestCase extends SolrTestCaseJ4 {
}
+ // equivalent integer numbers
+ if ((a instanceof Integer || a instanceof Long) && (b instanceof Integer || b instanceof Long)) {
+ if (((Number)a).longValue() == ((Number)b).longValue()) {
+ return null;
+ } else {
+ return ":" + a + "!=" + b;
+ }
+ }
+
if ((flags & FUZZY) != 0) {
if ((a instanceof Double && b instanceof Double)) {
double aaa = ((Double) a).doubleValue();