From c1c070bb8abd114488de7f5932306a6e7bf1b0c5 Mon Sep 17 00:00:00 2001 From: Nick Shanin Date: Tue, 6 Oct 2026 01:56:48 +0000 Subject: [PATCH 1/4] SOLR-8240: reject f field mappings combined with mapUniqueKeyOnly=true --- .../solr/handler/loader/JsonLoader.java | 8 ++++++- .../apache/solr/handler/JsonLoaderTest.java | 24 +++++++++++++++++++ ...transforming-and-indexing-custom-json.adoc | 1 + 3 files changed, 32 insertions(+), 1 deletion(-) 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/org/apache/solr/handler/JsonLoaderTest.java b/solr/core/src/test/org/apache/solr/handler/JsonLoaderTest.java index 04726e2f3a95..8387c53383a6 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,30 @@ 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")); + } + 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`:: + From fad7a1dd8e2397330e0973e809aeba60490d2f08 Mon Sep 17 00:00:00 2001 From: Nick Shanin Date: Tue, 6 Oct 2026 01:56:49 +0000 Subject: [PATCH 2/4] SOLR-8240: add changelog fragment --- .../SOLR-8240-map-unique-key-only-f-conflict.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changelog/unreleased/SOLR-8240-map-unique-key-only-f-conflict.yml 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 From 97cd22159ba0d2dd2de9f8a5bdb52de93bd2bffa Mon Sep 17 00:00:00 2001 From: Nick Shanin Date: Thu, 15 Oct 2026 03:52:42 -0400 Subject: [PATCH 3/4] SOLR-8240: cover inherited and overridden mapUniqueKeyOnly with field mappings --- ...solrconfig-update-json-docs-initparams.xml | 45 +++++++++++++++++++ .../org/apache/solr/core/TestInitParams.java | 39 ++++++++++++++++ .../apache/solr/handler/JsonLoaderTest.java | 15 +++++++ 3 files changed, 99 insertions(+) create mode 100644 solr/core/src/test-files/solr/collection1/conf/solrconfig-update-json-docs-initparams.xml 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..549450a79457 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,38 @@ public void testMatchPath() { assertTrue(initParams.matchPath("/update/json")); assertTrue(initParams.matchPath("/update")); } + + @Test + public void testUpdateJsonDocsInitParamsRejectsFieldMappings() throws Exception { + initCore("solrconfig-update-json-docs-initparams.xml", "schema.xml"); + try { + SolrRequestHandler handler = h.getCore().getRequestHandler("/update/json/docs"); + SolrException ex = + expectThrows( + SolrException.class, + () -> + handler.handleRequest( + jsonDocsRequest("f", "title:/title"), new SolrQueryResponse())); + assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); + assertTrue(ex.getMessage(), ex.getMessage().contains("mapUniqueKeyOnly")); + + SolrQueryResponse rsp = new SolrQueryResponse(); + handler.handleRequest( + jsonDocsRequest("mapUniqueKeyOnly", "false", "echo", "true", "f", "title:/title"), rsp); + assertEquals("[{title=t}]", String.valueOf(rsp.getValues().get("docs"))); + } finally { + 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 8387c53383a6..23c651a8d52e 100644 --- a/solr/core/src/test/org/apache/solr/handler/JsonLoaderTest.java +++ b/solr/core/src/test/org/apache/solr/handler/JsonLoaderTest.java @@ -213,6 +213,21 @@ public void testFieldMappingWithMapUniqueKeyOnlyIsRejected() { 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(); From 78ecddf01f5c76736d415b9d031095f22aa38b17 Mon Sep 17 00:00:00 2001 From: Nick Shanin Date: Fri, 16 Oct 2026 02:45:55 +0000 Subject: [PATCH 4/4] SOLR-8240: fix TestInitParams assertion shape and harness leak in the initParams test --- .../org/apache/solr/core/TestInitParams.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) 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 549450a79457..1fdea0721b93 100644 --- a/solr/core/src/test/org/apache/solr/core/TestInitParams.java +++ b/solr/core/src/test/org/apache/solr/core/TestInitParams.java @@ -135,23 +135,24 @@ public void testMatchPath() { @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"); - SolrException ex = - expectThrows( - SolrException.class, - () -> - handler.handleRequest( - jsonDocsRequest("f", "title:/title"), new SolrQueryResponse())); - assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code()); + 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")); - SolrQueryResponse rsp = new SolrQueryResponse(); + 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"); } }