You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ml...@apache.org on 2026/03/26 04:01:49 UTC

(solr) branch branch_10x updated: [SOLR-18114] CloudSolrClient fails deleteById with directUpdatesToLeadersOnly but no route passed (#4219)

This is an automated email from the ASF dual-hosted git repository.

mlbiscoc pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_10x by this push:
     new 8299ae01ac2 [SOLR-18114] CloudSolrClient fails deleteById with directUpdatesToLeadersOnly but no route passed (#4219)
8299ae01ac2 is described below

commit 8299ae01ac2573f692290c3dd7a500c37fd62ebf
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Wed Mar 25 22:42:58 2026 -0400

    [SOLR-18114] CloudSolrClient fails deleteById with directUpdatesToLeadersOnly but no route passed (#4219)
    
    Fixed a bug where CloudSolrClient throws an exception when deleteById is sent a to collection that has router field configured and has directUpdatesToLeadersOnly=true but is sent without routing info. Also added a log.warn when request is sent but no routing info is passed causing request to be sent in the general unoptimized route.
---
 changelog/unreleased/SOLR-18114.yml                |  8 +++
 .../solr/client/solrj/impl/CloudSolrClient.java    | 17 +++++++
 .../solrj/impl/CloudSolrClientRoutingTest.java     | 59 ++++++++++++++++++++++
 3 files changed, 84 insertions(+)

diff --git a/changelog/unreleased/SOLR-18114.yml b/changelog/unreleased/SOLR-18114.yml
new file mode 100644
index 00000000000..cd0e66a96cb
--- /dev/null
+++ b/changelog/unreleased/SOLR-18114.yml
@@ -0,0 +1,8 @@
+# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
+title: Fixed CloudSolrClient deleteById failure when routing info is not passed with compositeId router, router.field, and directUpdatesToLeadersOnly enabled
+type: fixed # added, changed, fixed, deprecated, removed, dependency_update, security, other
+authors:
+  - name: Matthew Biscocho
+links:
+  - name: SOLR-18114
+    url: https://issues.apache.org/jira/browse/SOLR-18114
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
index 60a80e15634..4f71b0cba79 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
@@ -771,6 +771,9 @@ public abstract class CloudSolrClient extends SolrClient {
             "directUpdatesToLeadersOnly==true but could not find leader(s)");
       } else {
         // we could not find a leader or routes yet - use unoptimized general path
+        log.warn(
+            "No routing info found for update to collection '{}', broadcasting to all shards.",
+            collection);
         return null;
       }
     }
@@ -1801,6 +1804,10 @@ public abstract class CloudSolrClient extends SolrClient {
     return results;
   }
 
+  /**
+   * Determines whether an UpdateRequest contains sufficient routing information to identify shard
+   * leaders for direct updates when directUpdatesToLeadersOnly is enabled.
+   */
   private static boolean hasInfoToFindLeaders(UpdateRequest updateRequest, String idField) {
     final Map<SolrInputDocument, Map<String, Object>> documents = updateRequest.getDocumentsMap();
     final Map<String, Map<String, Object>> deleteById = updateRequest.getDeleteByIdMap();
@@ -1823,6 +1830,16 @@ public abstract class CloudSolrClient extends SolrClient {
       }
     }
 
+    if (deleteById != null) {
+      for (final Map.Entry<String, Map<String, Object>> entry : deleteById.entrySet()) {
+        final Map<String, Object> params = entry.getValue();
+        if (params == null || params.get(ShardParams._ROUTE_) == null) {
+          // deleteById entry lacks explicit route parameter, can't find leader for it
+          return false;
+        }
+      }
+    }
+
     return true;
   }
 }
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRoutingTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRoutingTest.java
index 242bfd4261e..a2693167833 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRoutingTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientRoutingTest.java
@@ -18,17 +18,22 @@ package org.apache.solr.client.solrj.impl;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
+import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
+import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ShardParams;
 import org.apache.solr.common.params.UpdateParams;
 import org.apache.solr.common.util.NamedList;
+import org.apache.solr.util.LogListener;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -130,4 +135,58 @@ public final class CloudSolrClientRoutingTest extends SolrCloudTestCase {
     assertEquals(0, numForwardedWithRoute);
     assertEquals(100, numForwardedWithoutRoute);
   }
+
+  @Test
+  public void testDeleteWithoutRouteWithDirectUpdatesToLeadersOnly() throws Exception {
+    final String collectionName = "delete_without_route_collection";
+
+    final String docId = String.valueOf(random().nextInt(1000));
+    final String routeValue = TestUtil.randomRealisticUnicodeString(random());
+
+    CollectionAdminRequest.createCollection(collectionName, "conf", 2, 1)
+        .setRouterField("router_field_s")
+        .process(cluster.getSolrClient());
+    cluster.waitForActiveCollection(collectionName, 2, 2);
+
+    try (CloudSolrClient client =
+        new CloudSolrClient.Builder(
+                Collections.singletonList(cluster.getZkServer().getZkAddress()), Optional.empty())
+            .withDefaultCollection(collectionName)
+            .sendUpdatesOnlyToShardLeaders()
+            .sendDirectUpdatesToShardLeadersOnly()
+            .build()) {
+
+      UpdateRequest addRequest = new UpdateRequest();
+      addRequest.add("id", docId, "router_field_s", routeValue);
+      addRequest.process(client);
+      client.commit();
+
+      assertEquals(1, client.query(new SolrQuery("id:" + docId)).getResults().getNumFound());
+
+      try (LogListener warnLog =
+          LogListener.warn(CloudSolrClient.class)
+              .substring(
+                  "No routing info found for update to collection '"
+                      + collectionName
+                      + "', broadcasting to all shards.")) {
+
+        // Delete by ID without providing explicit route
+        // Should still delete via sending to all shards and log a warning
+        UpdateRequest deleteRequest = new UpdateRequest();
+        deleteRequest.deleteById(docId);
+        deleteRequest.process(client);
+        client.commit();
+
+        assertEquals(0, client.query(new SolrQuery("id:" + docId)).getResults().getNumFound());
+
+        // Verify the warning was logged when routing info was missing
+        assertNotNull(
+            "Expected at least one warning about missing routing info for deleteById",
+            warnLog.pollMessage());
+
+        // Clear any remaining messages
+        warnLog.clearQueue();
+      }
+    }
+  }
 }