diff --git a/changelog/unreleased/SOLR-8240-map-unique-key-only-f-conflict.yml b/changelog/unreleased/SOLR-8240-map-unique-key-only-f-conflict.yml
new file mode 100644
index 000000000000..f59194247b52
--- /dev/null
+++ b/changelog/unreleased/SOLR-8240-map-unique-key-only-f-conflict.yml
@@ -0,0 +1,8 @@
+title: >
+ /update/json/docs now rejects field mapping parameters (f) combined with mapUniqueKeyOnly=true with a 400 error instead of silently ignoring the mappings.
+type: changed
+authors:
+ - name: Nick Shanin
+links:
+ - name: SOLR-8240
+ url: https://issues.apache.org/jira/browse/SOLR-8240
diff --git a/solr/core/src/java/org/apache/solr/handler/loader/JsonLoader.java b/solr/core/src/java/org/apache/solr/handler/loader/JsonLoader.java
index a5a6feab2cb1..00246e9b1a44 100644
--- a/solr/core/src/java/org/apache/solr/handler/loader/JsonLoader.java
+++ b/solr/core/src/java/org/apache/solr/handler/loader/JsonLoader.java
@@ -246,10 +246,16 @@ void processUpdate(Reader reader) throws IOException {
private void handleSplitMode(String split, String[] fields, final Reader reader)
throws IOException {
if (split == null) split = "/";
+ final boolean mapUniqueKeyOnly = req.getParams().getBool("mapUniqueKeyOnly", false);
+ if (mapUniqueKeyOnly && fields != null && fields.length > 0) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST,
+ "Field mappings (f) are ignored when mapUniqueKeyOnly=true; set mapUniqueKeyOnly=false"
+ + " to map fields");
+ }
if (fields == null || fields.length == 0) fields = new String[] {"$FQN:/**"};
final boolean echo = "true".equals(req.getParams().get("echo"));
final String srcField = req.getParams().get("srcField");
- final boolean mapUniqueKeyOnly = req.getParams().getBool("mapUniqueKeyOnly", false);
if (srcField != null) {
if (!"/".equals(split))
throw new SolrException(
diff --git a/solr/core/src/test-files/solr/collection1/conf/solrconfig-update-json-docs-initparams.xml b/solr/core/src/test-files/solr/collection1/conf/solrconfig-update-json-docs-initparams.xml
new file mode 100644
index 000000000000..1a5fd08434b5
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-update-json-docs-initparams.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ ${solr.data.dir:}
+
+
+
+
+ ${tests.luceneMatchVersion:LATEST}
+
+
+
+
+
+ ${solr.commitwithin.softcommit:true}
+
+
+
+
+
+ true
+ _catch_all
+
+
+
+
diff --git a/solr/core/src/test/org/apache/solr/core/TestInitParams.java b/solr/core/src/test/org/apache/solr/core/TestInitParams.java
index 89ae2fb08e13..1fdea0721b93 100644
--- a/solr/core/src/test/org/apache/solr/core/TestInitParams.java
+++ b/solr/core/src/test/org/apache/solr/core/TestInitParams.java
@@ -18,9 +18,14 @@
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.util.NamedList;
+import org.apache.solr.request.SolrQueryRequestBase;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.SolrQueryResponse;
import org.junit.BeforeClass;
@@ -127,4 +132,39 @@ public void testMatchPath() {
assertTrue(initParams.matchPath("/update/json"));
assertTrue(initParams.matchPath("/update"));
}
+
+ @Test
+ public void testUpdateJsonDocsInitParamsRejectsFieldMappings() throws Exception {
+ deleteCore();
+ initCore("solrconfig-update-json-docs-initparams.xml", "schema.xml");
+ try {
+ SolrRequestHandler handler = h.getCore().getRequestHandler("/update/json/docs");
+ SolrQueryResponse rsp = new SolrQueryResponse();
+ handler.handleRequest(jsonDocsRequest("f", "title:/title"), rsp);
+ Exception ex = rsp.getException();
+ assertNotNull("expected the handler to record a rejection on the response", ex);
+ assertTrue(ex.toString(), ex instanceof SolrException);
+ assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ((SolrException) ex).code());
+ assertTrue(ex.getMessage(), ex.getMessage().contains("mapUniqueKeyOnly"));
+
+ rsp = new SolrQueryResponse();
+ handler.handleRequest(
+ jsonDocsRequest("mapUniqueKeyOnly", "false", "echo", "true", "f", "title:/title"), rsp);
+ assertEquals("[{title=t}]", String.valueOf(rsp.getValues().get("docs")));
+ } finally {
+ deleteCore();
+ initCore("solrconfig-paramset.xml", "schema.xml");
+ }
+ }
+
+ private SolrQueryRequestBase jsonDocsRequest(String... keysAndValues) {
+ ModifiableSolrParams solrParams = params("json.command", "false");
+ for (int i = 0; i < keysAndValues.length; i += 2) {
+ solrParams.set(keysAndValues[i], keysAndValues[i + 1]);
+ }
+ SolrQueryRequestBase req = new SolrQueryRequestBase(h.getCore(), solrParams) {};
+ String json = "{\"id\":\"1\",\"title\":\"t\"}";
+ req.setContentStreams(List.of(new ContentStreamBase.StringStream(json, "application/json")));
+ return req;
+ }
}
diff --git a/solr/core/src/test/org/apache/solr/handler/JsonLoaderTest.java b/solr/core/src/test/org/apache/solr/handler/JsonLoaderTest.java
index 04726e2f3a95..23c651a8d52e 100644
--- a/solr/core/src/test/org/apache/solr/handler/JsonLoaderTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/JsonLoaderTest.java
@@ -189,6 +189,45 @@ public void testInvalidJsonProducesBadRequestSolrException() {
assertTrue(ex.getMessage().contains("JSON"));
}
+ @Test
+ public void testFieldMappingWithMapUniqueKeyOnlyIsRejected() {
+ String json = "{'id':'1','title':'t'}".replace('\'', '"');
+ SolrQueryResponse rsp = new SolrQueryResponse();
+ BufferingRequestProcessor p = new BufferingRequestProcessor(null);
+ JsonLoader loader = new JsonLoader();
+
+ SolrException ex =
+ expectThrows(
+ SolrException.class,
+ () ->
+ loader.load(
+ req(
+ "json.command", "false",
+ "mapUniqueKeyOnly", "true",
+ "df", "_catch_all",
+ "f", "title:/title"),
+ rsp,
+ new ContentStreamBase.StringStream(json),
+ p));
+ assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code());
+ assertTrue(ex.getMessage(), ex.getMessage().contains("mapUniqueKeyOnly"));
+ }
+
+ @Test
+ public void testFieldMappingWithMapUniqueKeyOnlyFalseIsAccepted() throws Exception {
+ String json = "{'id':'1','title':'t'}".replace('\'', '"');
+ SolrQueryResponse rsp = new SolrQueryResponse();
+ BufferingRequestProcessor p = new BufferingRequestProcessor(null);
+ JsonLoader loader = new JsonLoader();
+ loader.load(
+ req("json.command", "false", "mapUniqueKeyOnly", "false", "f", "title:/title"),
+ rsp,
+ new ContentStreamBase.StringStream(json),
+ p);
+ assertEquals(1, p.addCommands.size());
+ assertEquals("t", p.addCommands.get(0).solrDoc.getFieldValue("title"));
+ }
+
public void testSimpleFormatInAdd() throws Exception {
String str = "{'add':[{'id':'1'},{'id':'2'}]}".replace('\'', '"');
SolrQueryRequest req = req();
diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/transforming-and-indexing-custom-json.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/transforming-and-indexing-custom-json.adoc
index 3a4fc8c081a6..02349199c82b 100644
--- a/solr/solr-ref-guide/modules/indexing-guide/pages/transforming-and-indexing-custom-json.adoc
+++ b/solr/solr-ref-guide/modules/indexing-guide/pages/transforming-and-indexing-custom-json.adoc
@@ -65,6 +65,7 @@ Wildcards can be used here, see <> below for mo
This parameter is particularly convenient when the fields in the input JSON are not available in the schema and xref:schemaless-mode.adoc[schemaless mode] is not enabled.
This will index all the fields into the default search field (using the `df` parameter) and only the `uniqueKey` field is mapped to the corresponding field in the schema.
If the input JSON does not have a value for the `uniqueKey` field then a UUID is generated for the same.
+Field mapping parameters (`f`) cannot be combined with this parameter: a request that supplies both is rejected with a 400 error, since the mappings would otherwise be silently ignored.
`df`::
+