dsmiley commented on code in PR #4640:
URL: https://github.com/apache/solr/pull/4640#discussion_r3708683324
##########
solr/solrj/src/java/org/apache/solr/client/solrj/response/json/JsonMapResponseParser.java:
##########
@@ -64,8 +68,23 @@ public Set<String> getContentTypes() {
return CONTENT_TYPES;
}
+ private static final SolrParams REQUEST_PARAMS =
+ new MapSolrParams(Map.of(JsonTextWriter.JSON_NL_STYLE,
JsonTextWriter.JSON_NL_MAP));
Review Comment:
see `SolrParams.of(...`
##########
solr/solrj/src/test/org/apache/solr/client/solrj/response/ResponseParserCanonicalResponseTest.java:
##########
@@ -0,0 +1,103 @@
+/*
+ * 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.client.solrj.response;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Map;
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.client.solrj.response.json.JsonMapResponseParser;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.JsonTextWriter;
+import org.apache.solr.common.util.NamedList;
+import org.junit.Test;
+
+/**
+ * Pins the {@link ResponseParser#processCanonicalResponse} contract: whatever
a parser's natural
+ * output looks like, this method returns the canonical shape the SolrJ
response classes read — a
+ * NamedList tree with SolrDocumentList for document sections. The conversion
belongs to the parser,
+ * so a client does not need to know which parsers require it.
+ */
+public class ResponseParserCanonicalResponseTest extends SolrTestCase {
+
+ private static final String JSON =
+ """
+ {"responseHeader":{"status":0,"QTime":1},\
+
"response":{"numFound":1,"start":0,"numFoundExact":true,"docs":[{"id":"1"}]}}""";
+
+ private static InputStream json() {
+ return new ByteArrayInputStream(JSON.getBytes(UTF_8));
+ }
+
+ /** The JSON map parser's own output is raw: Maps where the response classes
expect NamedLists. */
+ @Test
+ public void testJsonMapParserRawOutputIsNotCanonical() throws Exception {
+ NamedList<Object> raw = new
JsonMapResponseParser().processResponse(json(), null);
+ assertTrue("raw header should be a Map", raw.get("responseHeader")
instanceof Map);
+ assertFalse(
+ "raw header should not be a NamedList", raw.get("responseHeader")
instanceof NamedList);
+ assertFalse(
+ "raw response should not be a SolrDocumentList",
+ raw.get("response") instanceof SolrDocumentList);
+ }
+
+ /** ... and processCanonicalResponse converts it, without the caller asking.
*/
+ @Test
+ public void testJsonMapParserCanonicalResponseIsConverted() throws Exception
{
+ NamedList<Object> out = new
JsonMapResponseParser().processCanonicalResponse(json(), null);
+ assertTrue("header must be a NamedList", out.get("responseHeader")
instanceof NamedList);
+ assertTrue(
+ "response must be a SolrDocumentList", out.get("response") instanceof
SolrDocumentList);
+ assertEquals(1, ((SolrDocumentList) out.get("response")).getNumFound());
+ }
+
+ /** Parsers that are canonical already inherit the default and are unchanged
by it. */
+ @Test
+ public void testCanonicalParsersPassThrough() throws Exception {
+ String xml =
+ """
+ <?xml version="1.0" encoding="UTF-8"?>
+ <response><lst name="responseHeader"><int
name="status">0</int></lst></response>""";
+ NamedList<Object> out =
+ new XMLResponseParser()
+ .processCanonicalResponse(new
ByteArrayInputStream(xml.getBytes(UTF_8)), null);
+ assertTrue("header must be a NamedList", out.get("responseHeader")
instanceof NamedList);
+ }
+
+ /**
+ * A parser that needs the response written a particular way supplies that
param itself, rather
+ * than relying on every caller to know it. The JSON map parser needs {@code
json.nl=map}: under
+ * the default {@code flat} a NamedList arrives as an array of alternating
names and values, whose
+ * structure cannot be recovered.
+ */
+ @Test
+ public void testJsonMapParserRequestsNlMap() {
+ SolrParams params = new JsonMapResponseParser().getRequestParams();
+ assertNotNull("the JSON map parser must ask for a recoverable NamedList
form", params);
+ assertEquals(JsonTextWriter.JSON_NL_MAP,
params.get(JsonTextWriter.JSON_NL_STYLE));
+ }
Review Comment:
IMO this test doesn't add value. It unit tests a fine detail using more
code than the non-test. Instead we want to see an integration test showing the
correct behavior that this underlying detail here helps arrange for -- i.e. the
circumstance that led to the need to have this.
##########
solr/solrj/src/java/org/apache/solr/client/solrj/response/ResponseParser.java:
##########
@@ -50,6 +51,17 @@ private boolean validateContentTypes() {
/** The writer type placed onto the request as the {@code wt} param. */
public abstract String getWriterType(); // for example: wt=XML, JSON, etc
+ /**
+ * Params this parser needs on the request for its own reads, applied
alongside {@code wt}.
+ *
+ * <p>A parser that needs the response written a particular way returns
those params here rather
+ * than relying on callers to set them. Anything the caller set explicitly
wins, so this only
+ * supplies defaults. Returns null when the parser needs nothing beyond
{@code wt}.
+ */
+ public SolrParams getRequestParams() {
Review Comment:
Lets name this `getAdditionalRequestParams`.
##########
solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java:
##########
@@ -139,6 +140,16 @@ protected ModifiableSolrParams initializeSolrParams(
// The parser 'wt=' param is used instead of the original params
ModifiableSolrParams wparams = new
ModifiableSolrParams(solrRequest.getParams());
wparams.set(CommonParams.WT, parserToUse.getWriterType());
+ // Params the parser needs for its own reads, without overriding what the
request already set.
+ SolrParams parserParams = parserToUse.getRequestParams();
+ if (parserParams != null) {
+ for (Iterator<String> it = parserParams.getParameterNamesIterator();
it.hasNext(); ) {
+ String name = it.next();
+ if (wparams.get(name) == null) {
+ wparams.set(name, parserParams.getParams(name));
+ }
+ }
Review Comment:
why loop the names when you could just call
`parserParams.get(CommonParams.WT)` ?
##########
changelog/unreleased/SOLR-17316-response-parsers.yml:
##########
@@ -3,7 +3,9 @@
title: >
SolrJ's QueryResponse and other response objects now work when the client is
configured with a
non-binary response parser (such as the JSON parser); previously their
accessors could throw a
- ClassCastException.
+ ClassCastException, and nested documents were unreachable. A ResponseParser
can now declare the
+ request params it needs via getRequestParams(); JsonMapResponseParser uses
this to ask for
Review Comment:
Not worth putting here; it's an implementation detail. What you wrote
originally was very good.
##########
solr/solrj/src/test/org/apache/solr/client/solrj/response/TestSuggesterResponse.java:
##########
Review Comment:
thanks for this. It's good.
However, note that most of Lucene & Solr's randomized testing is done at a
deeper level such that an individual test generally doesn't even have to do
anything to get the randomization -- it just happens at a deeper test
framework/infra level. For example... imagine if the default was a settable
static supplier... and imagine if SolrTestCase were to set it. Then the useful
test coverage would go through the roof (thousands of Solr tests) and we'd
probably toss aside more of your tests as redundant. I'm not sure I want to
say we should do precisely this... but I'm at least informing you of the rather
unique randomized testing philosophy that permeates the Lucene & Solr projects.
##########
solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleJsonMapTest.java:
##########
@@ -14,28 +14,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.solr.client.solrj.response;
+package org.apache.solr.client.solrj;
-import org.apache.solr.SolrTestCase;
+import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
import org.apache.solr.client.solrj.response.json.JsonMapResponseParser;
-import org.junit.Test;
-/**
- * Pins the producesCanonicalForm() contract that gates response
normalization: only the JSON map
- * parser (which yields raw Maps/Lists) needs normalizing; the parsers that
already produce the
- * canonical NamedList/SolrDocumentList shape must report true so they pass
through untouched.
- */
-public class ResponseParserCanonicalFormTest extends SolrTestCase {
-
- @Test
- public void testCanonicalParsersReportTrue() {
- assertTrue(new JavaBinResponseParser().producesCanonicalForm());
- assertTrue(new XMLResponseParser().producesCanonicalForm());
- assertTrue(new InputStreamResponseParser("json").producesCanonicalForm());
- }
-
- @Test
- public void testJsonMapParserReportsFalse() {
- assertFalse(new JsonMapResponseParser().producesCanonicalForm());
+/** Runs the example tests over {@link JsonMapResponseParser}. */
Review Comment:
Niiiiice.... :-) thank you
##########
solr/solrj/src/test/org/apache/solr/client/solrj/response/ResponseParserCanonicalResponseTest.java:
##########
@@ -0,0 +1,103 @@
+/*
+ * 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.client.solrj.response;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Map;
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.client.solrj.response.json.JsonMapResponseParser;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.JsonTextWriter;
+import org.apache.solr.common.util.NamedList;
+import org.junit.Test;
+
+/**
+ * Pins the {@link ResponseParser#processCanonicalResponse} contract: whatever
a parser's natural
+ * output looks like, this method returns the canonical shape the SolrJ
response classes read — a
+ * NamedList tree with SolrDocumentList for document sections. The conversion
belongs to the parser,
+ * so a client does not need to know which parsers require it.
+ */
+public class ResponseParserCanonicalResponseTest extends SolrTestCase {
+
+ private static final String JSON =
+ """
+ {"responseHeader":{"status":0,"QTime":1},\
+
"response":{"numFound":1,"start":0,"numFoundExact":true,"docs":[{"id":"1"}]}}""";
+
+ private static InputStream json() {
+ return new ByteArrayInputStream(JSON.getBytes(UTF_8));
+ }
+
+ /** The JSON map parser's own output is raw: Maps where the response classes
expect NamedLists. */
+ @Test
+ public void testJsonMapParserRawOutputIsNotCanonical() throws Exception {
+ NamedList<Object> raw = new
JsonMapResponseParser().processResponse(json(), null);
+ assertTrue("raw header should be a Map", raw.get("responseHeader")
instanceof Map);
+ assertFalse(
+ "raw header should not be a NamedList", raw.get("responseHeader")
instanceof NamedList);
+ assertFalse(
+ "raw response should not be a SolrDocumentList",
+ raw.get("response") instanceof SolrDocumentList);
+ }
+
+ /** ... and processCanonicalResponse converts it, without the caller asking.
*/
+ @Test
+ public void testJsonMapParserCanonicalResponseIsConverted() throws Exception
{
+ NamedList<Object> out = new
JsonMapResponseParser().processCanonicalResponse(json(), null);
+ assertTrue("header must be a NamedList", out.get("responseHeader")
instanceof NamedList);
+ assertTrue(
+ "response must be a SolrDocumentList", out.get("response") instanceof
SolrDocumentList);
+ assertEquals(1, ((SolrDocumentList) out.get("response")).getNumFound());
+ }
+
+ /** Parsers that are canonical already inherit the default and are unchanged
by it. */
+ @Test
+ public void testCanonicalParsersPassThrough() throws Exception {
+ String xml =
+ """
+ <?xml version="1.0" encoding="UTF-8"?>
+ <response><lst name="responseHeader"><int
name="status">0</int></lst></response>""";
+ NamedList<Object> out =
+ new XMLResponseParser()
+ .processCanonicalResponse(new
ByteArrayInputStream(xml.getBytes(UTF_8)), null);
+ assertTrue("header must be a NamedList", out.get("responseHeader")
instanceof NamedList);
+ }
+
+ /**
+ * A parser that needs the response written a particular way supplies that
param itself, rather
+ * than relying on every caller to know it. The JSON map parser needs {@code
json.nl=map}: under
+ * the default {@code flat} a NamedList arrives as an array of alternating
names and values, whose
+ * structure cannot be recovered.
+ */
+ @Test
+ public void testJsonMapParserRequestsNlMap() {
+ SolrParams params = new JsonMapResponseParser().getRequestParams();
+ assertNotNull("the JSON map parser must ask for a recoverable NamedList
form", params);
+ assertEquals(JsonTextWriter.JSON_NL_MAP,
params.get(JsonTextWriter.JSON_NL_STYLE));
+ }
+
+ /** Parsers that need nothing beyond wt contribute no params. */
Review Comment:
again; no value
##########
solr/solrj/src/test/org/apache/solr/client/solrj/response/ResponseNormalizerTest.java:
##########
Review Comment:
I see you are supporting anonymous child documents -- which is the original
thing and I've been meaning to deprecate it. Nowadays, we do "nested
documents", which have named relationships from parent to child. Neither is
reflected in the schema, but anyway you can fetch children (named **or**
anonymous) via `fl=*,[child fl=*]` if I recall off the top of my head.
##########
solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java:
##########
@@ -139,6 +140,16 @@ protected ModifiableSolrParams initializeSolrParams(
// The parser 'wt=' param is used instead of the original params
ModifiableSolrParams wparams = new
ModifiableSolrParams(solrRequest.getParams());
wparams.set(CommonParams.WT, parserToUse.getWriterType());
+ // Params the parser needs for its own reads, without overriding what the
request already set.
+ SolrParams parserParams = parserToUse.getRequestParams();
+ if (parserParams != null) {
+ for (Iterator<String> it = parserParams.getParameterNamesIterator();
it.hasNext(); ) {
+ String name = it.next();
+ if (wparams.get(name) == null) {
+ wparams.set(name, parserParams.getParams(name));
+ }
+ }
Review Comment:
I can see you (really the LLMs you use :-) ) are unfamiliar with
`SolrParams.wrapDefaults` use that. Even has the null check.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]