From 04eb4ef911fc91fc0e2f348e732e52279b39000c Mon Sep 17 00:00:00 2001 From: Nick Shanin Date: Tue, 6 Oct 2026 22:05:29 -0400 Subject: [PATCH 1/6] SOLR-6973: skip signature computation for partial updates without signature fields Hypothetical, unrun regression test; see SOLR-6973-TESTING.md. --- .../SignatureUpdateProcessorFactory.java | 6 ++++ .../SignatureUpdateProcessorFactoryTest.java | 35 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/solr/core/src/java/org/apache/solr/update/processor/SignatureUpdateProcessorFactory.java b/solr/core/src/java/org/apache/solr/update/processor/SignatureUpdateProcessorFactory.java index fd99601357db..75d82fd5de61 100644 --- a/solr/core/src/java/org/apache/solr/update/processor/SignatureUpdateProcessorFactory.java +++ b/solr/core/src/java/org/apache/solr/update/processor/SignatureUpdateProcessorFactory.java @@ -188,6 +188,12 @@ public void processAdd(AddUpdateCommand cmd) throws IOException { } } + if (isPartialUpdate) { + // none of the signature fields are in the partial update, so there is nothing to sign; + // an empty signature would overwrite or delete unrelated documents + if (next != null) next.processAdd(cmd); + return; + } byte[] signature = sig.getSignature(); char[] arr = new char[signature.length << 1]; for (int i = 0; i < signature.length; i++) { diff --git a/solr/core/src/test/org/apache/solr/update/processor/SignatureUpdateProcessorFactoryTest.java b/solr/core/src/test/org/apache/solr/update/processor/SignatureUpdateProcessorFactoryTest.java index 05487931e063..1056f7527174 100644 --- a/solr/core/src/test/org/apache/solr/update/processor/SignatureUpdateProcessorFactoryTest.java +++ b/solr/core/src/test/org/apache/solr/update/processor/SignatureUpdateProcessorFactoryTest.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import org.apache.lucene.util.Constants; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.client.solrj.request.JavaBinRequestWriter; @@ -34,6 +35,7 @@ import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrQueryRequestBase; import org.apache.solr.response.SolrQueryResponse; +import org.apache.solr.update.AddUpdateCommand; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -200,6 +202,39 @@ public void run() { checkNumDocs(1); } + @Test + public void testPartialUpdateWithoutSignatureFieldsIsPassedThrough() throws Exception { + SignatureUpdateProcessorFactory factory = new SignatureUpdateProcessorFactory(); + NamedList args = new NamedList<>(); + args.add("signatureField", "signatureField"); + args.add("overwriteDupes", true); + args.add("fields", "v_t,t_field"); + args.add("signatureClass", "org.apache.solr.update.processor.Lookup3Signature"); + factory.init(args); + + AtomicReference seen = new AtomicReference<>(); + try (SolrQueryRequest r = req()) { + UpdateRequestProcessor proc = + factory.getInstance( + r, + new SolrQueryResponse(), + new UpdateRequestProcessor(null) { + @Override + public void processAdd(AddUpdateCommand cmd) { + seen.set(cmd); + } + }); + AddUpdateCommand cmd = new AddUpdateCommand(r); + cmd.solrDoc = new SolrInputDocument(); + cmd.solrDoc.addField("id", "1a"); + cmd.solrDoc.addField("name", Map.of("set", "changed")); + proc.processAdd(cmd); + } + assertNotNull(seen.get()); + assertNull(seen.get().solrDoc.getField("signatureField")); + assertNull(seen.get().updateTerm); + } + /** a non-indexed signatureField is fine as long as overwriteDupes==false */ @Test public void testNonIndexedSignatureField() throws Exception { From 3830106a006942ffb8f21c846c57b43a8dfa80b8 Mon Sep 17 00:00:00 2001 From: Nick Shanin Date: Tue, 6 Oct 2026 22:05:31 -0400 Subject: [PATCH 2/6] SOLR-6973: add changelog fragment --- .../unreleased/SOLR-6973-signature-partial-update.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changelog/unreleased/SOLR-6973-signature-partial-update.yml diff --git a/changelog/unreleased/SOLR-6973-signature-partial-update.yml b/changelog/unreleased/SOLR-6973-signature-partial-update.yml new file mode 100644 index 000000000000..ff040e46c82b --- /dev/null +++ b/changelog/unreleased/SOLR-6973-signature-partial-update.yml @@ -0,0 +1,8 @@ +title: SignatureUpdateProcessorFactory no longer adds an empty signature to (or deletes duplicates for) a partial update that contains none of the signature fields. +type: fixed +authors: + - name: Nick Shanin + nick: nick-boss-tech +links: + - name: SOLR-6973 + url: https://issues.apache.org/jira/browse/SOLR-6973 From 4c6092614e5a8985d19813007dfefcac2e80fbcc Mon Sep 17 00:00:00 2001 From: Nick Shanin Date: Tue, 6 Oct 2026 22:05:36 -0400 Subject: [PATCH 3/6] SOLR-6973: add hypothetical-reproduction handoff doc --- SOLR-6973-TESTING.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 SOLR-6973-TESTING.md diff --git a/SOLR-6973-TESTING.md b/SOLR-6973-TESTING.md new file mode 100644 index 000000000000..eca8bbb7d91f --- /dev/null +++ b/SOLR-6973-TESTING.md @@ -0,0 +1,19 @@ +# SOLR-6973 - hypothetical reproduction (NOT RUN) + +Guessed, never compiled or executed. No Gradle was run. + +JIRA: with `SignatureUpdateProcessorFactory` (overwriteDupes=true), about 5% of "set Processed=true" updates did not take effect. +Erickson's reply pointed at shard routing; the config also uses explicit `fields`. + +Hypothesis read from the code: for a partial (atomic) update that contains none of the configured signature fields, the loop never +throws (it throws only when a signature field IS present), so a signature of zero fields is computed, written into `signatureField` +and used as `updateTerm`. That signature is the same for every such partial update, so it can collide with unrelated documents. + +Change: a partial update with no signature fields is passed to the next processor untouched. + +Test: `SignatureUpdateProcessorFactoryTest.testPartialUpdateWithoutSignatureFieldsIsPassedThrough` (capturing next processor). + +Guesses to verify first: +- `AtomicUpdateDocumentMerger.isAtomicUpdate` recognises `Map.of("set", ...)` on field `name`. +- `AddUpdateCommand.solrDoc` is a public field in this version. +- The reporter's real cause may still be routing (ticket not conclusive). From fa5b59ba07b47fca7bd513ef619bec4632595b98 Mon Sep 17 00:00:00 2001 From: Nick Shanin Date: Fri, 9 Oct 2026 03:35:15 +0000 Subject: [PATCH 4/6] SOLR-6973: remove the handoff testing note at packaging --- SOLR-6973-TESTING.md | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 SOLR-6973-TESTING.md diff --git a/SOLR-6973-TESTING.md b/SOLR-6973-TESTING.md deleted file mode 100644 index eca8bbb7d91f..000000000000 --- a/SOLR-6973-TESTING.md +++ /dev/null @@ -1,19 +0,0 @@ -# SOLR-6973 - hypothetical reproduction (NOT RUN) - -Guessed, never compiled or executed. No Gradle was run. - -JIRA: with `SignatureUpdateProcessorFactory` (overwriteDupes=true), about 5% of "set Processed=true" updates did not take effect. -Erickson's reply pointed at shard routing; the config also uses explicit `fields`. - -Hypothesis read from the code: for a partial (atomic) update that contains none of the configured signature fields, the loop never -throws (it throws only when a signature field IS present), so a signature of zero fields is computed, written into `signatureField` -and used as `updateTerm`. That signature is the same for every such partial update, so it can collide with unrelated documents. - -Change: a partial update with no signature fields is passed to the next processor untouched. - -Test: `SignatureUpdateProcessorFactoryTest.testPartialUpdateWithoutSignatureFieldsIsPassedThrough` (capturing next processor). - -Guesses to verify first: -- `AtomicUpdateDocumentMerger.isAtomicUpdate` recognises `Map.of("set", ...)` on field `name`. -- `AddUpdateCommand.solrDoc` is a public field in this version. -- The reporter's real cause may still be routing (ticket not conclusive). From 51fcd05e695ae1a5983ed69a3d2afc50e7f7bc1e Mon Sep 17 00:00:00 2001 From: Nick Shanin Date: Sat, 10 Oct 2026 00:05:51 +0000 Subject: [PATCH 5/6] SOLR-6973: check for signature fields before constructing the signature --- .../SignatureUpdateProcessorFactory.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/update/processor/SignatureUpdateProcessorFactory.java b/solr/core/src/java/org/apache/solr/update/processor/SignatureUpdateProcessorFactory.java index 75d82fd5de61..e895bfb5d215 100644 --- a/solr/core/src/java/org/apache/solr/update/processor/SignatureUpdateProcessorFactory.java +++ b/solr/core/src/java/org/apache/solr/update/processor/SignatureUpdateProcessorFactory.java @@ -162,6 +162,22 @@ public void processAdd(AddUpdateCommand cmd) throws IOException { currDocSigFields = sigFields; } + if (isPartialUpdate) { + for (String field : currDocSigFields) { + if (doc.getField(field) != null) { + throw new SolrException( + ErrorCode.SERVER_ERROR, + "Can't use SignatureUpdateProcessor with partial update request " + + "containing signature field: " + + field); + } + } + // none of the signature fields are in the partial update, so there is nothing to sign; + // an empty signature would overwrite or delete unrelated documents + if (next != null) next.processAdd(cmd); + return; + } + Signature sig = req.getCore().getResourceLoader().newInstance(signatureClass, Signature.class); sig.init(params); @@ -169,13 +185,6 @@ public void processAdd(AddUpdateCommand cmd) throws IOException { for (String field : currDocSigFields) { SolrInputField f = doc.getField(field); if (f != null) { - if (isPartialUpdate) { - throw new SolrException( - ErrorCode.SERVER_ERROR, - "Can't use SignatureUpdateProcessor with partial update request " - + "containing signature field: " - + field); - } sig.add(field); Object o = f.getValue(); if (o instanceof Collection) { @@ -188,12 +197,6 @@ public void processAdd(AddUpdateCommand cmd) throws IOException { } } - if (isPartialUpdate) { - // none of the signature fields are in the partial update, so there is nothing to sign; - // an empty signature would overwrite or delete unrelated documents - if (next != null) next.processAdd(cmd); - return; - } byte[] signature = sig.getSignature(); char[] arr = new char[signature.length << 1]; for (int i = 0; i < signature.length; i++) { From ce7fe746038ea2be7bcc09a0640b459e2f4bb167 Mon Sep 17 00:00:00 2001 From: Nick Shanin Date: Thu, 15 Oct 2026 17:47:57 +0000 Subject: [PATCH 6/6] SOLR-6973: remove the nick line from the changelog fragment --- changelog/unreleased/SOLR-6973-signature-partial-update.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog/unreleased/SOLR-6973-signature-partial-update.yml b/changelog/unreleased/SOLR-6973-signature-partial-update.yml index ff040e46c82b..36cb04a0f974 100644 --- a/changelog/unreleased/SOLR-6973-signature-partial-update.yml +++ b/changelog/unreleased/SOLR-6973-signature-partial-update.yml @@ -2,7 +2,6 @@ title: SignatureUpdateProcessorFactory no longer adds an empty signature to (or type: fixed authors: - name: Nick Shanin - nick: nick-boss-tech links: - name: SOLR-6973 url: https://issues.apache.org/jira/browse/SOLR-6973