From 7b437c77f1092ac5c329bf1490ba532d6213d7ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Mar 2026 13:45:58 +0000 Subject: [PATCH 01/40] Initial plan From 89fdf9db36981dee5de9aa1e1e721a75aaa1f2ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:27:05 +0000 Subject: [PATCH 02/40] Migrate UpdateAPI from @EndPoint to JAX-RS annotations - Add UpdateApi JAX-RS interface in solr/api module with @Path, @POST, @Operation annotations for /update, /update/json, /update/xml, /update/csv, /update/bin endpoints - Rewrite UpdateAPI as a JerseyResource implementing UpdateApi; delegates to UpdateRequestHandler; adds UpdateRequestHandlerConfig inner class (APIConfigProvider.APIConfig) for injection - Update V2UpdateRequestHandler to implement APIConfigProvider and use getJerseyResources() instead of getApis() - Replace mock-based V2UpdateAPIMappingTest with UpdateAPITest integration test using SolrJettyTestRule Co-authored-by: epugh <22395+epugh@users.noreply.github.com> --- .../solr/client/api/endpoint/UpdateApi.java | 69 +++++++++ .../solr/handler/V2UpdateRequestHandler.java | 30 ++-- .../solr/handler/admin/api/UpdateAPI.java | 93 ++++++++---- .../apache/solr/handler/UpdateAPITest.java | 138 ++++++++++++++++++ .../solr/handler/V2UpdateAPIMappingTest.java | 118 --------------- 5 files changed, 296 insertions(+), 152 deletions(-) create mode 100644 solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java create mode 100644 solr/core/src/test/org/apache/solr/handler/UpdateAPITest.java delete mode 100644 solr/core/src/test/org/apache/solr/handler/V2UpdateAPIMappingTest.java diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java new file mode 100644 index 000000000000..b29c4bac83e7 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java @@ -0,0 +1,69 @@ +/* + * 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.api.endpoint; + +import static org.apache.solr.client.api.util.Constants.INDEX_PATH_PREFIX; + +import io.swagger.v3.oas.annotations.Operation; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.client.api.util.StoreApiParameters; + +/** V2 API definitions for indexing documents via the update handler. */ +@Path(INDEX_PATH_PREFIX + "/update") +public interface UpdateApi { + + @POST + @StoreApiParameters + @Operation( + summary = "Add, delete, or update documents using any supported content type", + tags = {"update"}) + SolrJerseyResponse update() throws Exception; + + @POST + @Path("/json") + @StoreApiParameters + @Operation( + summary = "Add, delete, or update documents in JSON format", + tags = {"update"}) + SolrJerseyResponse updateJson() throws Exception; + + @POST + @Path("/xml") + @StoreApiParameters + @Operation( + summary = "Add, delete, or update documents in XML format", + tags = {"update"}) + SolrJerseyResponse updateXml() throws Exception; + + @POST + @Path("/csv") + @StoreApiParameters + @Operation( + summary = "Add, delete, or update documents in CSV format", + tags = {"update"}) + SolrJerseyResponse updateCsv() throws Exception; + + @POST + @Path("/bin") + @StoreApiParameters + @Operation( + summary = "Add, delete, or update documents in JavaBin format", + tags = {"update"}) + SolrJerseyResponse updateBin() throws Exception; +} diff --git a/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java index d4bbae9b7b6e..7b33cc5c69ac 100644 --- a/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java @@ -18,26 +18,28 @@ package org.apache.solr.handler; import java.util.Collection; -import org.apache.solr.api.AnnotatedApi; -import org.apache.solr.api.Api; +import java.util.List; +import org.apache.solr.api.JerseyResource; import org.apache.solr.handler.admin.api.UpdateAPI; +import org.apache.solr.jersey.APIConfigProvider; /** - * An extension of {@link UpdateRequestHandler} used solely to register the v2 /update APIs + * An extension of {@link UpdateRequestHandler} used solely to register the v2 /update APIs. * *

At core-load time, Solr looks at each 'plugin' in ImplicitPlugins.json, fetches the v2 {@link - * Api} implementations associated with each RequestHandler, and registers them in an {@link - * org.apache.solr.api.ApiBag}. Since UpdateRequestHandler is mentioned multiple times in - * ImplicitPlugins.json (once for each update API: /update, /update/json, etc.), this would cause + * org.apache.solr.api.Api} implementations associated with each RequestHandler, and registers them + * in an {@link org.apache.solr.api.ApiBag}. Since UpdateRequestHandler is mentioned multiple times + * in ImplicitPlugins.json (once for each update API: /update, /update/json, etc.), this would cause * the v2 APIs to be registered in duplicate. To avoid this, Solr has this RequestHandler, whose * only purpose is to register the v2 APIs that conceptually should be associated with * UpdateRequestHandler. */ -public class V2UpdateRequestHandler extends UpdateRequestHandler { +public class V2UpdateRequestHandler extends UpdateRequestHandler + implements APIConfigProvider { @Override - public Collection getApis() { - return AnnotatedApi.getApis(new UpdateAPI(this)); + public Collection> getJerseyResources() { + return List.of(UpdateAPI.class); } @Override @@ -49,4 +51,14 @@ public Boolean registerV1() { public Boolean registerV2() { return Boolean.TRUE; } + + @Override + public UpdateAPI.UpdateRequestHandlerConfig provide() { + return new UpdateAPI.UpdateRequestHandlerConfig(this); + } + + @Override + public Class getConfigClass() { + return UpdateAPI.UpdateRequestHandlerConfig.class; + } } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java index e97198dec080..2785f56e4802 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java @@ -17,52 +17,95 @@ package org.apache.solr.handler.admin.api; -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; import static org.apache.solr.common.params.CommonParams.PATH; import static org.apache.solr.security.PermissionNameProvider.Name.UPDATE_PERM; -import org.apache.solr.api.EndPoint; +import jakarta.inject.Inject; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.endpoint.UpdateApi; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.common.SolrException; import org.apache.solr.handler.UpdateRequestHandler; +import org.apache.solr.jersey.APIConfigProvider; +import org.apache.solr.jersey.PermissionName; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; /** - * All v2 APIs that share a prefix of /update + * V2 API implementation for indexing documents. * - *

Most of these v2 APIs are implemented as pure "pass-throughs" to the v1 code paths, but there - * are a few exceptions: /update and /update/json are both rewritten to /update/json/docs. + *

These APIs delegate to the v1 {@link UpdateRequestHandler}. The {@code /update} and {@code + * /update/json} paths are rewritten to {@code /update/json/docs} so that JSON arrays of documents + * are processed by the JSON loader rather than the update-command loader. */ -public class UpdateAPI { +public class UpdateAPI extends JerseyResource implements UpdateApi { + private final UpdateRequestHandler updateRequestHandler; + private final SolrQueryRequest solrQueryRequest; + private final SolrQueryResponse solrQueryResponse; + + @Inject + public UpdateAPI( + UpdateRequestHandlerConfig handlerConfig, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + this.updateRequestHandler = handlerConfig.updateRequestHandler; + this.solrQueryRequest = solrQueryRequest; + this.solrQueryResponse = solrQueryResponse; + } + + @Override + @PermissionName(UPDATE_PERM) + public SolrJerseyResponse update() throws Exception { + return handleUpdate(UpdateRequestHandler.DOC_PATH); + } + + @Override + @PermissionName(UPDATE_PERM) + public SolrJerseyResponse updateJson() throws Exception { + return handleUpdate(UpdateRequestHandler.DOC_PATH); + } - public UpdateAPI(UpdateRequestHandler updateRequestHandler) { - this.updateRequestHandler = updateRequestHandler; + @Override + @PermissionName(UPDATE_PERM) + public SolrJerseyResponse updateXml() throws Exception { + return handleUpdate(null); } - @EndPoint(method = POST, path = "/update", permission = UPDATE_PERM) - public void update(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - req.getContext().put(PATH, "/update/json/docs"); - updateRequestHandler.handleRequest(req, rsp); + @Override + @PermissionName(UPDATE_PERM) + public SolrJerseyResponse updateCsv() throws Exception { + return handleUpdate(null); } - @EndPoint(method = POST, path = "/update/xml", permission = UPDATE_PERM) - public void updateXml(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - updateRequestHandler.handleRequest(req, rsp); + @Override + @PermissionName(UPDATE_PERM) + public SolrJerseyResponse updateBin() throws Exception { + return handleUpdate(null); } - @EndPoint(method = POST, path = "/update/csv", permission = UPDATE_PERM) - public void updateCsv(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - updateRequestHandler.handleRequest(req, rsp); + private SolrJerseyResponse handleUpdate(String pathOverride) throws Exception { + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + if (pathOverride != null) { + solrQueryRequest.getContext().put(PATH, pathOverride); + } + updateRequestHandler.handleRequest(solrQueryRequest, solrQueryResponse); + rethrowAnyException(solrQueryResponse); + return response; } - @EndPoint(method = POST, path = "/update/json", permission = UPDATE_PERM) - public void updateJson(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - req.getContext().put(PATH, "/update/json/docs"); - updateRequestHandler.handleRequest(req, rsp); + private void rethrowAnyException(SolrQueryResponse rsp) { + final Exception ex = rsp.getException(); + if (ex instanceof SolrException solrEx) throw solrEx; + if (ex != null) throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, ex); } - @EndPoint(method = POST, path = "/update/bin", permission = UPDATE_PERM) - public void updateJavabin(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - updateRequestHandler.handleRequest(req, rsp); + /** Configuration object providing access to the {@link UpdateRequestHandler} instance. */ + public static class UpdateRequestHandlerConfig implements APIConfigProvider.APIConfig { + final UpdateRequestHandler updateRequestHandler; + + public UpdateRequestHandlerConfig(UpdateRequestHandler updateRequestHandler) { + this.updateRequestHandler = updateRequestHandler; + } } } diff --git a/solr/core/src/test/org/apache/solr/handler/UpdateAPITest.java b/solr/core/src/test/org/apache/solr/handler/UpdateAPITest.java new file mode 100644 index 000000000000..7a5ffa43f0aa --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/UpdateAPITest.java @@ -0,0 +1,138 @@ +/* + * 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.handler; + +import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrRequest; +import org.apache.solr.client.solrj.request.GenericV2SolrRequest; +import org.apache.solr.client.solrj.request.QueryRequest; +import org.apache.solr.client.solrj.request.RequestWriter; +import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.util.EnvUtils; +import org.apache.solr.util.ExternalPaths; +import org.apache.solr.util.SolrJettyTestRule; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +/** + * Integration tests for the v2 update API endpoints implemented via JAX-RS in {@link + * org.apache.solr.handler.admin.api.UpdateAPI}. Uses {@link SolrJettyTestRule} to run a real Solr + * instance. + */ +public class UpdateAPITest extends SolrTestCaseJ4 { + + @ClassRule public static SolrJettyTestRule solrTestRule = new SolrJettyTestRule(); + + private static final String CORE_NAME = DEFAULT_TEST_COLLECTION_NAME; + + @BeforeClass + public static void beforeClass() throws Exception { + EnvUtils.setProperty( + ALLOW_PATHS_SYSPROP, ExternalPaths.SERVER_HOME.toAbsolutePath().toString()); + solrTestRule.startSolr(createTempDir()); + solrTestRule + .newCollection(CORE_NAME) + .withConfigSet(ExternalPaths.TECHPRODUCTS_CONFIGSET) + .create(); + } + + @Test + public void testUpdateViaV2Api() throws Exception { + final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); + + // POST a JSON array of documents via the V2 /update endpoint (rewrites to /update/json/docs) + final GenericV2SolrRequest addReq = + new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); + addReq.setContentWriter( + new RequestWriter.StringPayloadContentWriter( + "[{\"id\":\"v2update1\",\"title\":\"V2 update test\"}]", "application/json")); + client.request(addReq); + + // Commit via V2 update endpoint + final GenericV2SolrRequest commitReq = + new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); + commitReq.setContentWriter( + new RequestWriter.StringPayloadContentWriter("{\"commit\":{}}", "application/json")); + client.request(commitReq); + + // Verify the document was indexed + final ModifiableSolrParams queryParams = new ModifiableSolrParams(); + queryParams.set("q", "id:v2update1"); + final QueryResponse queryRsp = new QueryRequest(queryParams).process(client, CORE_NAME); + assertEquals(1, queryRsp.getResults().getNumFound()); + } + + @Test + public void testUpdateJsonViaV2Api() throws Exception { + final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); + + // POST via the V2 /update/json endpoint (also rewrites to /update/json/docs) + final GenericV2SolrRequest addReq = + new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update/json"); + addReq.setContentWriter( + new RequestWriter.StringPayloadContentWriter( + "[{\"id\":\"v2updatejson1\",\"title\":\"V2 update/json test\"}]", "application/json")); + client.request(addReq); + + // Commit + final GenericV2SolrRequest commitReq = + new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); + commitReq.setContentWriter( + new RequestWriter.StringPayloadContentWriter("{\"commit\":{}}", "application/json")); + client.request(commitReq); + + // Verify + final ModifiableSolrParams queryParams = new ModifiableSolrParams(); + queryParams.set("q", "id:v2updatejson1"); + final QueryResponse queryRsp = new QueryRequest(queryParams).process(client, CORE_NAME); + assertEquals(1, queryRsp.getResults().getNumFound()); + } + + @Test + public void testUpdateXmlViaV2Api() throws Exception { + final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); + + // POST via the V2 /update/xml endpoint + final GenericV2SolrRequest addReq = + new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update/xml"); + addReq.setContentWriter( + new RequestWriter.StringPayloadContentWriter( + "v2updatexml1" + + "V2 update/xml test", + "application/xml")); + client.request(addReq); + + // Commit + final GenericV2SolrRequest commitReq = + new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); + commitReq.setContentWriter( + new RequestWriter.StringPayloadContentWriter("{\"commit\":{}}", "application/json")); + client.request(commitReq); + + // Verify + final ModifiableSolrParams queryParams = new ModifiableSolrParams(); + queryParams.set("q", "id:v2updatexml1"); + final QueryResponse queryRsp = new QueryRequest(queryParams).process(client, CORE_NAME); + assertEquals(1, queryRsp.getResults().getNumFound()); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/V2UpdateAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/V2UpdateAPIMappingTest.java deleted file mode 100644 index c3cce079c777..000000000000 --- a/solr/core/src/test/org/apache/solr/handler/V2UpdateAPIMappingTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.handler; - -import static org.apache.solr.common.params.CommonParams.PATH; -import static org.mockito.Mockito.mock; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.apache.solr.SolrTestCaseJ4; -import org.apache.solr.api.Api; -import org.apache.solr.api.ApiBag; -import org.apache.solr.common.params.ModifiableSolrParams; -import org.apache.solr.common.util.CommandOperation; -import org.apache.solr.handler.admin.api.UpdateAPI; -import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.request.SolrQueryRequestBase; -import org.apache.solr.response.SolrQueryResponse; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -/** Unit tests for the v2 to v1 mapping logic in {@link UpdateAPI} */ -public class V2UpdateAPIMappingTest extends SolrTestCaseJ4 { - private ApiBag apiBag; - - @BeforeClass - public static void ensureWorkingMockito() { - assumeWorkingMockito(); - } - - @Before - public void setupApiBag() { - UpdateRequestHandler mockUpdateHandler = mock(UpdateRequestHandler.class); - apiBag = new ApiBag(false); - final UpdateAPI updateAPI = new UpdateAPI(mockUpdateHandler); - - apiBag.registerObject(updateAPI); - } - - @Test - public void testUpdateApiRewriting() { - - // Assume JSON in path if no specific format specified - { - final SolrQueryRequest req = runUpdateApi("/update"); - assertEquals("/update/json/docs", req.getContext().get(PATH)); - } - - // Rewrite v2 /update/json to v1's /update/json/docs - { - final SolrQueryRequest req = runUpdateApi("/update/json"); - assertEquals("/update/json/docs", req.getContext().get(PATH)); - } - - // No rewriting for /update/xml, /update/csv, or /update/bin - { - final SolrQueryRequest req = runUpdateApi("/update/xml"); - assertEquals("/update/xml", req.getContext().get(PATH)); - } - { - final SolrQueryRequest req = runUpdateApi("/update/csv"); - assertEquals("/update/csv", req.getContext().get(PATH)); - } - { - final SolrQueryRequest req = runUpdateApi("/update/bin"); - assertEquals("/update/bin", req.getContext().get(PATH)); - } - } - - private SolrQueryRequest runUpdateApi(String path) { - final HashMap parts = new HashMap<>(); - final Api api = apiBag.lookup(path, "POST", parts); - final SolrQueryResponse rsp = new SolrQueryResponse(); - final SolrQueryRequestBase req = new SolrQueryRequestBase(null, new ModifiableSolrParams()); - req.getContext().put(PATH, path); - - api.call(req, rsp); - - return req; - } - - private SolrQueryRequestBase createTestRequest(Map pathTemplateValues) { - return new SolrQueryRequestBase(null, new ModifiableSolrParams()) { - @Override - public List getCommands(boolean validateInput) { - return Collections.emptyList(); - } - - @Override - public Map getPathTemplateValues() { - return pathTemplateValues; - } - - @Override - public String getHttpMethod() { - return "POST"; - } - }; - } -} From 863fbf171d26eeb5932e21116c5847ae2412fa0e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 01:25:53 +0000 Subject: [PATCH 03/40] Add NodeHealthAPITest2 integration test for V2 node health API Resolves failing test: NodeHealthAPITest2.testLegacyMode_WithoutMaxGenerationLagReturnsOk The test verifies that the V2 GET /api/node/health endpoint returns status=OK in legacy (standalone, non-ZooKeeper) mode when maxGenerationLag is not specified in the request. This exercises the null-maxGenerationLag code path in HealthCheckHandler.healthCheckLegacyMode() which immediately returns OK without checking replication lag. Test uses SolrJettyTestRule with no ZooKeeper to run in legacy mode. Co-authored-by: epugh <22395+epugh@users.noreply.github.com> --- .../handler/admin/api/NodeHealthAPITest2.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 solr/core/src/test/org/apache/solr/handler/admin/api/NodeHealthAPITest2.java diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/NodeHealthAPITest2.java b/solr/core/src/test/org/apache/solr/handler/admin/api/NodeHealthAPITest2.java new file mode 100644 index 000000000000..60ca6acb5c90 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/NodeHealthAPITest2.java @@ -0,0 +1,52 @@ +/* + * 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.handler.admin.api; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.solrj.request.V2Request; +import org.apache.solr.client.solrj.response.V2Response; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.util.SolrJettyTestRule; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +/** + * Integration tests for the {@link NodeHealthAPI} V2 endpoint using {@link SolrJettyTestRule} in + * standalone (legacy, non-ZooKeeper) mode. + */ +public class NodeHealthAPITest2 extends SolrTestCaseJ4 { + + @ClassRule public static SolrJettyTestRule solrTestRule = new SolrJettyTestRule(); + + @BeforeClass + public static void beforeClass() throws Exception { + solrTestRule.startSolr(createTempDir()); + } + + /** + * Verifies that the V2 node health API returns OK in legacy (standalone) mode when {@code + * maxGenerationLag} is not specified in the request. + */ + @Test + public void testLegacyMode_WithoutMaxGenerationLagReturnsOk() throws Exception { + final V2Response response = + new V2Request.Builder("/node/health").build().process(solrTestRule.getSolrClient(null)); + assertEquals(CommonParams.OK, response.getResponse().get(CommonParams.STATUS)); + } +} From f5e49ea028e415a8fb22a5d36393ee481e39f8c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 11:58:22 +0000 Subject: [PATCH 04/40] Fix TestDistributedTracing to use proper SolrJ add() instead of broken V2Request update/json The V2Request("/c/{collection}/update/json") call was broken after the UpdateAPI migration to JAX-RS: the JAX-RS path regex "cores|collections" doesn't match the "/c/" alias. Replace with the proper SolrJ cloudClient.add() (which goes through the v1 /update path) and update span name/collection assertions to match. Co-authored-by: epugh <22395+epugh@users.noreply.github.com> --- .../opentelemetry/TestDistributedTracing.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java index e3eb646896c8..245d703e9fe9 100644 --- a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java +++ b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java @@ -159,15 +159,17 @@ public void testV2Api() throws Exception { assertEquals("post:/collections/{collection}/reload", finishedSpans.get(0).getName()); assertCollectionName(finishedSpans.get(0), COLLECTION); - new V2Request.Builder("/c/" + COLLECTION + "/update/json") - .withMethod(SolrRequest.METHOD.POST) - .withPayload("{\n" + " \"id\" : \"9\"\n" + "}") - .withParams(params("commit", "true")) - .build() - .process(cloudClient); + cloudClient.add(COLLECTION, sdoc("id", "9")); finishedSpans = getAndClearSpans(); - assertEquals("post:/c/{collection}/update/json", finishedSpans.get(0).getName()); - assertCollectionName(finishedSpans.get(0), COLLECTION); + finishedSpans.removeIf( + span -> + span.getAttributes().get(TraceUtils.TAG_HTTP_URL) == null + || !span.getAttributes().get(TraceUtils.TAG_HTTP_URL).endsWith("/update")); + assertEquals("post:/{core}/update", finishedSpans.get(0).getName()); + assertCoreName(finishedSpans.get(0), COLLECTION); + + cloudClient.commit(COLLECTION); + getAndClearSpans(); final V2Response v2Response = new V2Request.Builder("/c/" + COLLECTION + "/select") From 3b79fb8f0db96cfd21ba343693ff4c3bb0041c31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:29:26 +0000 Subject: [PATCH 05/40] Support /c/ path alias in JAX-RS routing for all V2 endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Normalize /c/ → /collections/ in ContainerRequestUtils.getRequestUri() before Jersey sees the URI, fixing JAX-RS path matching for all endpoints that use INDEX_PATH_PREFIX (update, select, schema, etc.) with the /c/ shorthand. The tracing span name correctly preserves /c/{collection}/... since it is computed from V2HttpCall.pathSegments which use the original request path. Co-authored-by: epugh <22395+epugh@users.noreply.github.com> --- .../container/ContainerRequestUtils.java | 20 +++++++++++++++++++ .../opentelemetry/TestDistributedTracing.java | 18 ++++++++--------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/jersey/container/ContainerRequestUtils.java b/solr/core/src/java/org/apache/solr/jersey/container/ContainerRequestUtils.java index f3371c8f0e6c..e96433c0814b 100644 --- a/solr/core/src/java/org/apache/solr/jersey/container/ContainerRequestUtils.java +++ b/solr/core/src/java/org/apache/solr/jersey/container/ContainerRequestUtils.java @@ -124,6 +124,10 @@ private static URI getRequestUri(HttpServletRequest httpServletRequest, URI base // Jersey is only used for v2 APIs so we have no need of the janky v2 suffixing (and it impedes // matching) - remove if present. uri = uri.replace("/solr/____v2", ""); + // Normalize the /c/ alias to /collections/ so JAX-RS path matching works correctly. + // V2HttpCall already resolves the collection from /c/ paths, but Jersey sees the original URI + // and its path regex only matches 'cores' or 'collections', not the 'c' shorthand. + uri = normalizeCAlias(uri); final String queryString = httpServletRequest.getQueryString(); if (queryString != null) { @@ -140,4 +144,20 @@ private static String getServerAddress(URI baseUri) { } return serverAddress; } + + /** + * Normalizes the {@code /c/} collection alias to {@code /collections/} so that JAX-RS path + * matching works correctly. The V2 API supports {@code /c/} as a shorthand for {@code + * /collections/}, but JAX-RS path templates use the regex {@code cores|collections} to match the + * index-type segment. + */ + static String normalizeCAlias(String uri) { + if (uri.startsWith("/c/")) { + return "/collections/" + uri.substring(3); + } + if (uri.equals("/c")) { + return "/collections"; + } + return uri; + } } diff --git a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java index 245d703e9fe9..e3eb646896c8 100644 --- a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java +++ b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java @@ -159,17 +159,15 @@ public void testV2Api() throws Exception { assertEquals("post:/collections/{collection}/reload", finishedSpans.get(0).getName()); assertCollectionName(finishedSpans.get(0), COLLECTION); - cloudClient.add(COLLECTION, sdoc("id", "9")); + new V2Request.Builder("/c/" + COLLECTION + "/update/json") + .withMethod(SolrRequest.METHOD.POST) + .withPayload("{\n" + " \"id\" : \"9\"\n" + "}") + .withParams(params("commit", "true")) + .build() + .process(cloudClient); finishedSpans = getAndClearSpans(); - finishedSpans.removeIf( - span -> - span.getAttributes().get(TraceUtils.TAG_HTTP_URL) == null - || !span.getAttributes().get(TraceUtils.TAG_HTTP_URL).endsWith("/update")); - assertEquals("post:/{core}/update", finishedSpans.get(0).getName()); - assertCoreName(finishedSpans.get(0), COLLECTION); - - cloudClient.commit(COLLECTION); - getAndClearSpans(); + assertEquals("post:/c/{collection}/update/json", finishedSpans.get(0).getName()); + assertCollectionName(finishedSpans.get(0), COLLECTION); final V2Response v2Response = new V2Request.Builder("/c/" + COLLECTION + "/select") From 014880c0f28a5157151c7c49f0395487cec1571a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:45:29 +0000 Subject: [PATCH 06/40] Fix NPE in UpdateAPI: call pre/postDecorateResponse when delegating to UpdateRequestHandler DistributedZkUpdateProcessor.handleReplicationFactor() calls rsp.getResponseHeader().add(...), which requires the response header to be initialized by SolrCore.preDecorateResponse(). In the JAX-RS path, this was never called (only the old Api path calls it in executeCoreRequest). Add pre/postDecorateResponse calls around the updateRequestHandler.handleRequest() call in UpdateAPI.handleUpdate(), mirroring what SolrCore.execute() does for v1. Co-authored-by: epugh <22395+epugh@users.noreply.github.com> --- .../src/java/org/apache/solr/handler/admin/api/UpdateAPI.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java index 2785f56e4802..f5e65e1ef6d2 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java @@ -25,6 +25,7 @@ import org.apache.solr.client.api.endpoint.UpdateApi; import org.apache.solr.client.api.model.SolrJerseyResponse; import org.apache.solr.common.SolrException; +import org.apache.solr.core.SolrCore; import org.apache.solr.handler.UpdateRequestHandler; import org.apache.solr.jersey.APIConfigProvider; import org.apache.solr.jersey.PermissionName; @@ -89,7 +90,9 @@ private SolrJerseyResponse handleUpdate(String pathOverride) throws Exception { if (pathOverride != null) { solrQueryRequest.getContext().put(PATH, pathOverride); } + SolrCore.preDecorateResponse(solrQueryRequest, solrQueryResponse); updateRequestHandler.handleRequest(solrQueryRequest, solrQueryResponse); + SolrCore.postDecorateResponse(updateRequestHandler, solrQueryRequest, solrQueryResponse); rethrowAnyException(solrQueryResponse); return response; } From 3cc0accc830a325b1ccb917ab52fe5719edac1c2 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 2 Mar 2026 08:51:40 -0500 Subject: [PATCH 07/40] code warning cleanups --- .../solr/handler/admin/api/UpdateAPI.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java index f5e65e1ef6d2..8fefda58ca80 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java @@ -63,29 +63,29 @@ public SolrJerseyResponse update() throws Exception { @Override @PermissionName(UPDATE_PERM) - public SolrJerseyResponse updateJson() throws Exception { + public SolrJerseyResponse updateJson() { return handleUpdate(UpdateRequestHandler.DOC_PATH); } @Override @PermissionName(UPDATE_PERM) - public SolrJerseyResponse updateXml() throws Exception { + public SolrJerseyResponse updateXml() { return handleUpdate(null); } @Override @PermissionName(UPDATE_PERM) - public SolrJerseyResponse updateCsv() throws Exception { + public SolrJerseyResponse updateCsv() { return handleUpdate(null); } @Override @PermissionName(UPDATE_PERM) - public SolrJerseyResponse updateBin() throws Exception { + public SolrJerseyResponse updateBin() { return handleUpdate(null); } - private SolrJerseyResponse handleUpdate(String pathOverride) throws Exception { + private SolrJerseyResponse handleUpdate(String pathOverride) { final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); if (pathOverride != null) { solrQueryRequest.getContext().put(PATH, pathOverride); @@ -104,11 +104,7 @@ private void rethrowAnyException(SolrQueryResponse rsp) { } /** Configuration object providing access to the {@link UpdateRequestHandler} instance. */ - public static class UpdateRequestHandlerConfig implements APIConfigProvider.APIConfig { - final UpdateRequestHandler updateRequestHandler; - - public UpdateRequestHandlerConfig(UpdateRequestHandler updateRequestHandler) { - this.updateRequestHandler = updateRequestHandler; - } + public record UpdateRequestHandlerConfig(UpdateRequestHandler updateRequestHandler) + implements APIConfigProvider.APIConfig { } } From e40d417bfb7be840cb70036c55d075b286279a4e Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 2 Mar 2026 09:53:56 -0500 Subject: [PATCH 08/40] Lint --- .../java/org/apache/solr/handler/admin/api/UpdateAPI.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java index 8fefda58ca80..afdce20dec7f 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java @@ -104,7 +104,6 @@ private void rethrowAnyException(SolrQueryResponse rsp) { } /** Configuration object providing access to the {@link UpdateRequestHandler} instance. */ - public record UpdateRequestHandlerConfig(UpdateRequestHandler updateRequestHandler) - implements APIConfigProvider.APIConfig { - } + public record UpdateRequestHandlerConfig(UpdateRequestHandler updateRequestHandler) + implements APIConfigProvider.APIConfig {} } From 92d70a80774441a31254be203065e0068f1019bb Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 2 Mar 2026 09:54:39 -0500 Subject: [PATCH 09/40] We didn't need this. --- .../handler/admin/api/NodeHealthAPITest2.java | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 solr/core/src/test/org/apache/solr/handler/admin/api/NodeHealthAPITest2.java diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/NodeHealthAPITest2.java b/solr/core/src/test/org/apache/solr/handler/admin/api/NodeHealthAPITest2.java deleted file mode 100644 index 60ca6acb5c90..000000000000 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/NodeHealthAPITest2.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.handler.admin.api; - -import org.apache.solr.SolrTestCaseJ4; -import org.apache.solr.client.solrj.request.V2Request; -import org.apache.solr.client.solrj.response.V2Response; -import org.apache.solr.common.params.CommonParams; -import org.apache.solr.util.SolrJettyTestRule; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Test; - -/** - * Integration tests for the {@link NodeHealthAPI} V2 endpoint using {@link SolrJettyTestRule} in - * standalone (legacy, non-ZooKeeper) mode. - */ -public class NodeHealthAPITest2 extends SolrTestCaseJ4 { - - @ClassRule public static SolrJettyTestRule solrTestRule = new SolrJettyTestRule(); - - @BeforeClass - public static void beforeClass() throws Exception { - solrTestRule.startSolr(createTempDir()); - } - - /** - * Verifies that the V2 node health API returns OK in legacy (standalone) mode when {@code - * maxGenerationLag} is not specified in the request. - */ - @Test - public void testLegacyMode_WithoutMaxGenerationLagReturnsOk() throws Exception { - final V2Response response = - new V2Request.Builder("/node/health").build().process(solrTestRule.getSolrClient(null)); - assertEquals(CommonParams.OK, response.getResponse().get(CommonParams.STATUS)); - } -} From 74bdf15b88df70e29d4a79f4e8e9581aeb33f1fd Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 2 Mar 2026 10:12:02 -0500 Subject: [PATCH 10/40] Properly route reqeusts by returning an empty getApis --- .../org/apache/solr/handler/V2UpdateRequestHandler.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java index 7b33cc5c69ac..99f4c39f8c75 100644 --- a/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java @@ -18,7 +18,9 @@ package org.apache.solr.handler; import java.util.Collection; +import java.util.Collections; import java.util.List; +import org.apache.solr.api.Api; import org.apache.solr.api.JerseyResource; import org.apache.solr.handler.admin.api.UpdateAPI; import org.apache.solr.jersey.APIConfigProvider; @@ -36,6 +38,11 @@ */ public class V2UpdateRequestHandler extends UpdateRequestHandler implements APIConfigProvider { + + @Override + public Collection getApis() { + return Collections.emptyList(); + } @Override public Collection> getJerseyResources() { From 6a675bfb5ddc3d744416df811dd1b894e7338cf9 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 2 Mar 2026 10:15:27 -0500 Subject: [PATCH 11/40] current v2 doesnt support multiple commands --- .../apache/solr/handler/UpdateAPITest.java | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/handler/UpdateAPITest.java b/solr/core/src/test/org/apache/solr/handler/UpdateAPITest.java index 7a5ffa43f0aa..50d6e42d6055 100644 --- a/solr/core/src/test/org/apache/solr/handler/UpdateAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/UpdateAPITest.java @@ -68,12 +68,8 @@ public void testUpdateViaV2Api() throws Exception { "[{\"id\":\"v2update1\",\"title\":\"V2 update test\"}]", "application/json")); client.request(addReq); - // Commit via V2 update endpoint - final GenericV2SolrRequest commitReq = - new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); - commitReq.setContentWriter( - new RequestWriter.StringPayloadContentWriter("{\"commit\":{}}", "application/json")); - client.request(commitReq); + // Commit via standard SolrJ commit (v2 /update is docs-only and does not support commands) + client.commit(CORE_NAME); // Verify the document was indexed final ModifiableSolrParams queryParams = new ModifiableSolrParams(); @@ -94,12 +90,8 @@ public void testUpdateJsonViaV2Api() throws Exception { "[{\"id\":\"v2updatejson1\",\"title\":\"V2 update/json test\"}]", "application/json")); client.request(addReq); - // Commit - final GenericV2SolrRequest commitReq = - new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); - commitReq.setContentWriter( - new RequestWriter.StringPayloadContentWriter("{\"commit\":{}}", "application/json")); - client.request(commitReq); + // Commit via standard SolrJ commit (v2 /update is docs-only and does not support commands) + client.commit(CORE_NAME); // Verify final ModifiableSolrParams queryParams = new ModifiableSolrParams(); @@ -122,12 +114,8 @@ public void testUpdateXmlViaV2Api() throws Exception { "application/xml")); client.request(addReq); - // Commit - final GenericV2SolrRequest commitReq = - new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); - commitReq.setContentWriter( - new RequestWriter.StringPayloadContentWriter("{\"commit\":{}}", "application/json")); - client.request(commitReq); + // Commit via standard SolrJ commit (v2 /update is docs-only and does not support commands) + client.commit(CORE_NAME); // Verify final ModifiableSolrParams queryParams = new ModifiableSolrParams(); From 4d2cbc0d755be2211256f5958c0c8e0e39d1b539 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 2 Mar 2026 10:25:49 -0500 Subject: [PATCH 12/40] Relocate test to be in the same package as what we are testing. --- .../java/org/apache/solr/handler/V2UpdateRequestHandler.java | 2 +- .../solr/{handler => handler.admin.api}/UpdateAPITest.java | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) rename solr/core/src/test/org/apache/solr/{handler => handler.admin.api}/UpdateAPITest.java (97%) diff --git a/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java index 99f4c39f8c75..5f626e390b6b 100644 --- a/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java @@ -38,7 +38,7 @@ */ public class V2UpdateRequestHandler extends UpdateRequestHandler implements APIConfigProvider { - + @Override public Collection getApis() { return Collections.emptyList(); diff --git a/solr/core/src/test/org/apache/solr/handler/UpdateAPITest.java b/solr/core/src/test/org/apache/solr/handler.admin.api/UpdateAPITest.java similarity index 97% rename from solr/core/src/test/org/apache/solr/handler/UpdateAPITest.java rename to solr/core/src/test/org/apache/solr/handler.admin.api/UpdateAPITest.java index 50d6e42d6055..73c108124cb1 100644 --- a/solr/core/src/test/org/apache/solr/handler/UpdateAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler.admin.api/UpdateAPITest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.solr.handler; +package org.apache.solr.handler.admin.api; import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; @@ -36,8 +36,7 @@ /** * Integration tests for the v2 update API endpoints implemented via JAX-RS in {@link - * org.apache.solr.handler.admin.api.UpdateAPI}. Uses {@link SolrJettyTestRule} to run a real Solr - * instance. + * org.apache.solr.handler.admin.api.UpdateAPI}. */ public class UpdateAPITest extends SolrTestCaseJ4 { From 7337062f00397bea8274ccfd924e4ba6b7ad5fcc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:35:18 +0000 Subject: [PATCH 13/40] Add indexing-with-v2-apis.adoc documenting V2 update API endpoints Co-authored-by: epugh <22395+epugh@users.noreply.github.com> --- .../modules/indexing-guide/indexing-nav.adoc | 1 + .../pages/indexing-with-update-handlers.adoc | 5 +- .../pages/indexing-with-v2-apis.adoc | 183 ++++++++++++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc diff --git a/solr/solr-ref-guide/modules/indexing-guide/indexing-nav.adoc b/solr/solr-ref-guide/modules/indexing-guide/indexing-nav.adoc index 9b50849716c3..eda1c1b8be7f 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/indexing-nav.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/indexing-nav.adoc @@ -50,6 +50,7 @@ * Indexing & Data Operations ** xref:indexing-with-update-handlers.adoc[] *** xref:transforming-and-indexing-custom-json.adoc[] +*** xref:indexing-with-v2-apis.adoc[] ** xref:indexing-with-cbor.adoc[] ** xref:indexing-with-tika.adoc[] ** xref:indexing-nested-documents.adoc[] diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc index 68a5ede49358..2adf2ce8b8d0 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc @@ -1,5 +1,5 @@ = Indexing with Update Handlers -:page-children: transforming-and-indexing-custom-json +:page-children: transforming-and-indexing-custom-json, indexing-with-v2-apis // 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 @@ -20,6 +20,9 @@ Update handlers are request handlers designed to add, delete and update documents to the index. In addition to having plugins for importing rich documents (see xref:indexing-with-tika.adoc[]), Solr natively supports indexing structured documents in XML, CSV, and JSON. +NOTE: Solr also exposes update functionality via the xref:configuration-guide:v2-api.adoc[v2 API]. +See xref:indexing-with-v2-apis.adoc[] for details on the v2 update endpoints and their differences from the v1 handlers described on this page. + The recommended way to configure and use request handlers is with path based names that map to paths in the request URL. However, request handlers can also be specified with the `qt` (query type) parameter if the xref:configuration-guide:requestdispatcher.adoc[`requestDispatcher`] is appropriately configured. It is possible to access the same handler using more than one name, which can be useful if you wish to specify different sets of default options. diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc new file mode 100644 index 000000000000..cfd96c585254 --- /dev/null +++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc @@ -0,0 +1,183 @@ += Indexing with the V2 Update API +// 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. + +Solr's xref:configuration-guide:v2-api.adoc[v2 API] provides update endpoints under the `/api` path prefix. +These endpoints accept the same document formats as the xref:indexing-with-update-handlers.adoc[v1 update handlers], with some important differences described below. + +NOTE: The v2 API is classified as "experimental" and may change in backwards-incompatible ways. + +== V2 Update Endpoint Paths + +For a SolrCloud collection the v2 update base path is: + +---- +/api/collections/{collection}/update +---- + +For a standalone core the v2 update base path is: + +---- +/api/cores/{core}/update +---- + +The following sub-paths are available: + +[width="100%",options="header",] +|=== +|Path |Accepted Format |Notes +|`/update` |JSON (array of documents) |Equivalent to v1 `/update/json/docs`; document arrays only +|`/update/json` |JSON (array of documents) |Same behavior as `/update`; document arrays only +|`/update/xml` |XML |Supports full XML update syntax (add, delete, commit, optimize) +|`/update/csv` |CSV |Equivalent to v1 `/update/csv` +|`/update/bin` |JavaBin |Equivalent to v1 `/update` with `Content-Type: application/javabin` +|=== + +IMPORTANT: The v2 `/update` and `/update/json` endpoints are document-only: they process a JSON array of documents (like the v1 `/update/json/docs` path) and do *not* support the full xref:indexing-with-update-handlers.adoc#sending-json-update-commands[JSON update command syntax] (commit, delete, optimize within the request body). +Use `/update/xml` for those operations, or issue commit/rollback via separate API calls. + +== JSON Document Indexing + +The v2 `/update` and `/update/json` endpoints both accept a JSON array of documents: + +[source,bash] +---- +curl -X POST -H 'Content-Type: application/json' \ + 'http://localhost:8983/api/collections/my_collection/update?commit=true' \ + --data-binary ' +[ + { + "id": "1", + "title": "Doc 1" + }, + { + "id": "2", + "title": "Doc 2" + } +]' +---- + +A single document can also be posted as a JSON object: + +[source,bash] +---- +curl -X POST -H 'Content-Type: application/json' \ + 'http://localhost:8983/api/collections/my_collection/update?commit=true' \ + --data-binary ' +{ + "id": "1", + "title": "Doc 1" +}' +---- + +Query parameters such as `commit`, `commitWithin`, and `overwrite` can be appended to the URL, as shown above. + +== XML Document Indexing + +The v2 `/update/xml` endpoint accepts the same XML format as the v1 `/update` handler and supports the full XML update syntax, including add, delete, commit, and optimize commands. + +[source,bash] +---- +curl -X POST -H 'Content-Type: application/xml' \ + 'http://localhost:8983/api/collections/my_collection/update/xml' \ + --data-binary ' + + + 1 + Doc 1 + +' +---- + +Delete by ID and delete by query are also supported: + +[source,bash] +---- +curl -X POST -H 'Content-Type: application/xml' \ + 'http://localhost:8983/api/collections/my_collection/update/xml?commit=true' \ + --data-binary ' + + 1 + title:unwanted +' +---- + +== CSV Document Indexing + +The v2 `/update/csv` endpoint accepts CSV-formatted documents and supports the same xref:indexing-with-update-handlers.adoc#csv-update-parameters[CSV update parameters] as the v1 handler: + +[source,bash] +---- +curl -X POST -H 'Content-Type: text/csv' \ + 'http://localhost:8983/api/collections/my_collection/update/csv?commit=true' \ + --data-binary ' +id,title +1,Doc 1 +2,Doc 2' +---- + +== JavaBin Document Indexing + +The v2 `/update/bin` endpoint accepts documents in JavaBin format, which is the native binary format used by SolrJ. +This endpoint is primarily intended for use by SolrJ clients. + +== Commit and Rollback + +Because the v2 `/update` and `/update/json` endpoints do not support update commands in the request body, commits and rollbacks must be issued separately. +Use the `commit=true` or `commitWithin=N` URL parameters to commit after indexing: + +[source,bash] +---- +curl -X POST -H 'Content-Type: application/json' \ + 'http://localhost:8983/api/collections/my_collection/update?commit=true' \ + --data-binary '[{"id":"1","title":"Doc 1"}]' +---- + +For a soft commit: + +[source,bash] +---- +curl -X POST -H 'Content-Type: application/json' \ + 'http://localhost:8983/api/collections/my_collection/update?softCommit=true' \ + --data-binary '[{"id":"1","title":"Doc 1"}]' +---- + +Alternatively, use `/update/xml` to issue an explicit commit command: + +[source,bash] +---- +curl -X POST -H 'Content-Type: application/xml' \ + 'http://localhost:8983/api/collections/my_collection/update/xml' \ + --data-binary '' +---- + +== Comparison with V1 Update Handlers + +The table below summarizes the key differences between the v1 and v2 update endpoints. + +[width="100%",options="header",] +|=== +|Feature |V1 |V2 +|Base path |`/solr/{collection}/update` |`/api/collections/{collection}/update` +|JSON documents |`/update` or `/update/json/docs` |`/update` or `/update/json` +|JSON update commands (commit/delete/optimize) |`/update` |Not supported via `/update` or `/update/json`; use `/update/xml` or URL params +|XML updates |`/update` (via Content-Type) |`/update/xml` +|CSV updates |`/update/csv` |`/update/csv` +|JavaBin updates |`/update` (via Content-Type) |`/update/bin` +|=== + +For full details on the v1 update handlers, see xref:indexing-with-update-handlers.adoc[]. From b932cb296d3d924a70a26f19d0debd12f20e907c Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Mon, 2 Mar 2026 11:21:20 -0500 Subject: [PATCH 14/40] Tweak formatting --- .../indexing-guide/pages/indexing-with-v2-apis.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc index cfd96c585254..69709fdc8a8f 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc @@ -44,7 +44,7 @@ The following sub-paths are available: |`/update/json` |JSON (array of documents) |Same behavior as `/update`; document arrays only |`/update/xml` |XML |Supports full XML update syntax (add, delete, commit, optimize) |`/update/csv` |CSV |Equivalent to v1 `/update/csv` -|`/update/bin` |JavaBin |Equivalent to v1 `/update` with `Content-Type: application/javabin` +|`/update/bin` |Javabin |Equivalent to v1 `/update` with `Content-Type: application/javabin` |=== IMPORTANT: The v2 `/update` and `/update/json` endpoints are document-only: they process a JSON array of documents (like the v1 `/update/json/docs` path) and do *not* support the full xref:indexing-with-update-handlers.adoc#sending-json-update-commands[JSON update command syntax] (commit, delete, optimize within the request body). @@ -130,9 +130,9 @@ id,title 2,Doc 2' ---- -== JavaBin Document Indexing +== Javabin Document Indexing -The v2 `/update/bin` endpoint accepts documents in JavaBin format, which is the native binary format used by SolrJ. +The v2 `/update/bin` endpoint accepts documents in Javabin format, which is the native binary format used by SolrJ. This endpoint is primarily intended for use by SolrJ clients. == Commit and Rollback @@ -177,7 +177,7 @@ The table below summarizes the key differences between the v1 and v2 update endp |JSON update commands (commit/delete/optimize) |`/update` |Not supported via `/update` or `/update/json`; use `/update/xml` or URL params |XML updates |`/update` (via Content-Type) |`/update/xml` |CSV updates |`/update/csv` |`/update/csv` -|JavaBin updates |`/update` (via Content-Type) |`/update/bin` +|Javabin updates |`/update` (via Content-Type) |`/update/bin` |=== For full details on the v1 update handlers, see xref:indexing-with-update-handlers.adoc[]. From 055f200be5e28156d2f6cfc32a174c6390cc652a Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 3 Mar 2026 08:18:13 -0500 Subject: [PATCH 15/40] Update solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../indexing-guide/pages/indexing-with-v2-apis.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc index 69709fdc8a8f..c518307ed192 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc @@ -40,19 +40,19 @@ The following sub-paths are available: [width="100%",options="header",] |=== |Path |Accepted Format |Notes -|`/update` |JSON (array of documents) |Equivalent to v1 `/update/json/docs`; document arrays only -|`/update/json` |JSON (array of documents) |Same behavior as `/update`; document arrays only +|`/update` |JSON document or array of documents |Equivalent to v1 `/update/json/docs`; documents only (no JSON command syntax) +|`/update/json` |JSON document or array of documents |Same behavior as `/update`; documents only (no JSON command syntax) |`/update/xml` |XML |Supports full XML update syntax (add, delete, commit, optimize) |`/update/csv` |CSV |Equivalent to v1 `/update/csv` |`/update/bin` |Javabin |Equivalent to v1 `/update` with `Content-Type: application/javabin` |=== -IMPORTANT: The v2 `/update` and `/update/json` endpoints are document-only: they process a JSON array of documents (like the v1 `/update/json/docs` path) and do *not* support the full xref:indexing-with-update-handlers.adoc#sending-json-update-commands[JSON update command syntax] (commit, delete, optimize within the request body). +IMPORTANT: The v2 `/update` and `/update/json` endpoints are document-only: they process one or more JSON documents (either a single JSON object or an array of objects, like the v1 `/update/json/docs` path) and do *not* support the full xref:indexing-with-update-handlers.adoc#sending-json-update-commands[JSON update command syntax] (commit, delete, optimize within the request body). Use `/update/xml` for those operations, or issue commit/rollback via separate API calls. == JSON Document Indexing -The v2 `/update` and `/update/json` endpoints both accept a JSON array of documents: +The v2 `/update` and `/update/json` endpoints both accept JSON document(s). The example below shows an array of documents: [source,bash] ---- From 296f33aad220dbe81609a30b4f01e42d4bdd09e1 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 3 Mar 2026 08:24:04 -0500 Subject: [PATCH 16/40] relocate via intellij went werid --- .../org/apache/solr/client/api/endpoint/UpdateApi.java | 10 +++++----- .../admin/api}/UpdateAPITest.java | 0 2 files changed, 5 insertions(+), 5 deletions(-) rename solr/core/src/test/org/apache/solr/{handler.admin.api => handler/admin/api}/UpdateAPITest.java (100%) diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java index b29c4bac83e7..04bf7d37e935 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java @@ -31,7 +31,7 @@ public interface UpdateApi { @POST @StoreApiParameters @Operation( - summary = "Add, delete, or update documents using any supported content type", + summary = "Index documents using any supported content type", tags = {"update"}) SolrJerseyResponse update() throws Exception; @@ -39,7 +39,7 @@ public interface UpdateApi { @Path("/json") @StoreApiParameters @Operation( - summary = "Add, delete, or update documents in JSON format", + summary = "Index documents in JSON format", tags = {"update"}) SolrJerseyResponse updateJson() throws Exception; @@ -47,7 +47,7 @@ public interface UpdateApi { @Path("/xml") @StoreApiParameters @Operation( - summary = "Add, delete, or update documents in XML format", + summary = "Index documents in XML format", tags = {"update"}) SolrJerseyResponse updateXml() throws Exception; @@ -55,7 +55,7 @@ public interface UpdateApi { @Path("/csv") @StoreApiParameters @Operation( - summary = "Add, delete, or update documents in CSV format", + summary = "Index documents in CSV format", tags = {"update"}) SolrJerseyResponse updateCsv() throws Exception; @@ -63,7 +63,7 @@ public interface UpdateApi { @Path("/bin") @StoreApiParameters @Operation( - summary = "Add, delete, or update documents in JavaBin format", + summary = "Index documents documents in Javabin format", tags = {"update"}) SolrJerseyResponse updateBin() throws Exception; } diff --git a/solr/core/src/test/org/apache/solr/handler.admin.api/UpdateAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java similarity index 100% rename from solr/core/src/test/org/apache/solr/handler.admin.api/UpdateAPITest.java rename to solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java From 793eef056c094a2d1a40b23e63914694a54310dd Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Thu, 17 Sep 2026 15:00:31 -0400 Subject: [PATCH 17/40] /bin really should be /javabin. Open door to other binary protocols. --- .../solr/client/api/endpoint/UpdateApi.java | 6 ++--- .../solr/handler/V2UpdateRequestHandler.java | 3 +-- .../solr/handler/admin/api/UpdateAPI.java | 4 +-- .../solr/handler/admin/api/UpdateAPITest.java | 27 +++++++++++++++++++ .../pages/indexing-with-v2-apis.adoc | 6 ++--- 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java index 04bf7d37e935..f480ce58aba8 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java @@ -60,10 +60,10 @@ public interface UpdateApi { SolrJerseyResponse updateCsv() throws Exception; @POST - @Path("/bin") + @Path("/javabin") @StoreApiParameters @Operation( - summary = "Index documents documents in Javabin format", + summary = "Index documents in Javabin format", tags = {"update"}) - SolrJerseyResponse updateBin() throws Exception; + SolrJerseyResponse updateJavabin() throws Exception; } diff --git a/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java index 5f626e390b6b..2a55407d897f 100644 --- a/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java @@ -18,7 +18,6 @@ package org.apache.solr.handler; import java.util.Collection; -import java.util.Collections; import java.util.List; import org.apache.solr.api.Api; import org.apache.solr.api.JerseyResource; @@ -41,7 +40,7 @@ public class V2UpdateRequestHandler extends UpdateRequestHandler @Override public Collection getApis() { - return Collections.emptyList(); + return List.of(); } @Override diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java index afdce20dec7f..d18c75a44058 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java @@ -81,8 +81,8 @@ public SolrJerseyResponse updateCsv() { @Override @PermissionName(UPDATE_PERM) - public SolrJerseyResponse updateBin() { - return handleUpdate(null); + public SolrJerseyResponse updateJavabin() { + return handleUpdate(UpdateRequestHandler.BIN_PATH); } private SolrJerseyResponse handleUpdate(String pathOverride) { diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java index 73c108124cb1..8d4c718fb932 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java @@ -19,13 +19,17 @@ import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; +import java.io.ByteArrayOutputStream; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.request.GenericV2SolrRequest; +import org.apache.solr.client.solrj.request.JavaBinUpdateRequestCodec; import org.apache.solr.client.solrj.request.QueryRequest; import org.apache.solr.client.solrj.request.RequestWriter; +import org.apache.solr.client.solrj.request.UpdateRequest; import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.util.EnvUtils; import org.apache.solr.util.ExternalPaths; @@ -122,4 +126,27 @@ public void testUpdateXmlViaV2Api() throws Exception { final QueryResponse queryRsp = new QueryRequest(queryParams).process(client, CORE_NAME); assertEquals(1, queryRsp.getResults().getNumFound()); } + + @Test + public void testUpdateJavabinViaV2Api() throws Exception { + final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); + final SolrInputDocument doc = new SolrInputDocument(); + doc.setField("id", "v2updatejavabin1"); + final UpdateRequest updateRequest = new UpdateRequest(); + updateRequest.add(doc); + final ByteArrayOutputStream payload = new ByteArrayOutputStream(); + new JavaBinUpdateRequestCodec().marshal(updateRequest, payload); + + final GenericV2SolrRequest addReq = + new GenericV2SolrRequest( + SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update/javabin"); + addReq.withContent(payload.toByteArray(), "application/javabin"); + client.request(addReq); + client.commit(CORE_NAME); + + final ModifiableSolrParams queryParams = new ModifiableSolrParams(); + queryParams.set("q", "id:v2updatejavabin1"); + final QueryResponse queryRsp = new QueryRequest(queryParams).process(client, CORE_NAME); + assertEquals(1, queryRsp.getResults().getNumFound()); + } } diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc index c518307ed192..a1692a8e3b43 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc @@ -44,7 +44,7 @@ The following sub-paths are available: |`/update/json` |JSON document or array of documents |Same behavior as `/update`; documents only (no JSON command syntax) |`/update/xml` |XML |Supports full XML update syntax (add, delete, commit, optimize) |`/update/csv` |CSV |Equivalent to v1 `/update/csv` -|`/update/bin` |Javabin |Equivalent to v1 `/update` with `Content-Type: application/javabin` +|`/update/javabin` |Javabin |Equivalent to v1 `/update` with `Content-Type: application/javabin` |=== IMPORTANT: The v2 `/update` and `/update/json` endpoints are document-only: they process one or more JSON documents (either a single JSON object or an array of objects, like the v1 `/update/json/docs` path) and do *not* support the full xref:indexing-with-update-handlers.adoc#sending-json-update-commands[JSON update command syntax] (commit, delete, optimize within the request body). @@ -132,7 +132,7 @@ id,title == Javabin Document Indexing -The v2 `/update/bin` endpoint accepts documents in Javabin format, which is the native binary format used by SolrJ. +The v2 `/update/javabin` endpoint accepts documents in Javabin format, which is the native binary format used by SolrJ. This endpoint is primarily intended for use by SolrJ clients. == Commit and Rollback @@ -177,7 +177,7 @@ The table below summarizes the key differences between the v1 and v2 update endp |JSON update commands (commit/delete/optimize) |`/update` |Not supported via `/update` or `/update/json`; use `/update/xml` or URL params |XML updates |`/update` (via Content-Type) |`/update/xml` |CSV updates |`/update/csv` |`/update/csv` -|Javabin updates |`/update` (via Content-Type) |`/update/bin` +|Javabin updates |`/update` (via Content-Type) |`/update/javabin` |=== For full details on the v1 update handlers, see xref:indexing-with-update-handlers.adoc[]. From 9d87a154bbf51cea5d7b85455bfdd805f0c3292d Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Thu, 17 Sep 2026 20:49:00 -0400 Subject: [PATCH 18/40] Fixed this code, thanks copilot. Not committing the new tests that prove not needed. --- .../src/java/org/apache/solr/handler/admin/api/UpdateAPI.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java index d18c75a44058..4054c3c465b0 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java @@ -25,7 +25,6 @@ import org.apache.solr.client.api.endpoint.UpdateApi; import org.apache.solr.client.api.model.SolrJerseyResponse; import org.apache.solr.common.SolrException; -import org.apache.solr.core.SolrCore; import org.apache.solr.handler.UpdateRequestHandler; import org.apache.solr.jersey.APIConfigProvider; import org.apache.solr.jersey.PermissionName; @@ -90,9 +89,7 @@ private SolrJerseyResponse handleUpdate(String pathOverride) { if (pathOverride != null) { solrQueryRequest.getContext().put(PATH, pathOverride); } - SolrCore.preDecorateResponse(solrQueryRequest, solrQueryResponse); updateRequestHandler.handleRequest(solrQueryRequest, solrQueryResponse); - SolrCore.postDecorateResponse(updateRequestHandler, solrQueryRequest, solrQueryResponse); rethrowAnyException(solrQueryResponse); return response; } From 3dab0ef4cc440fbb9dd4a7b63ee132fd4fed6ec5 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Thu, 17 Sep 2026 20:51:55 -0400 Subject: [PATCH 19/40] add changelog --- .../unreleased/SOLR-18457-migrate-update-api-to-jax-rs.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog/unreleased/SOLR-18457-migrate-update-api-to-jax-rs.yml diff --git a/changelog/unreleased/SOLR-18457-migrate-update-api-to-jax-rs.yml b/changelog/unreleased/SOLR-18457-migrate-update-api-to-jax-rs.yml new file mode 100644 index 000000000000..9a2335479977 --- /dev/null +++ b/changelog/unreleased/SOLR-18457-migrate-update-api-to-jax-rs.yml @@ -0,0 +1,7 @@ +title: Migrate v2 update endpoints to JAX-RS and rename the Javabin endpoint to /update/javabin +type: changed +authors: + - name: Eric Pugh +links: + - name: SOLR-18457 + url: https://issues.apache.org/jira/browse/SOLR-18457 From 3ed21ddb0b50cdedc0ef489cc277f2f3cc6881da Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Fri, 18 Sep 2026 06:35:07 -0400 Subject: [PATCH 20/40] Restore the versions=true support that v1 equivalent has, and so did v2 non jax --- .../solr/client/api/endpoint/UpdateApi.java | 12 ++-- .../solr/client/api/model/UpdateResponse.java | 37 +++++++++++ .../solr/handler/admin/api/UpdateAPI.java | 33 +++++++--- .../solr/handler/admin/api/UpdateAPITest.java | 63 ++++++++++++++++++- 4 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 solr/api/src/java/org/apache/solr/client/api/model/UpdateResponse.java diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java index f480ce58aba8..267e47bf9dc3 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java @@ -21,7 +21,7 @@ import io.swagger.v3.oas.annotations.Operation; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; -import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.client.api.model.UpdateResponse; import org.apache.solr.client.api.util.StoreApiParameters; /** V2 API definitions for indexing documents via the update handler. */ @@ -33,7 +33,7 @@ public interface UpdateApi { @Operation( summary = "Index documents using any supported content type", tags = {"update"}) - SolrJerseyResponse update() throws Exception; + UpdateResponse update() throws Exception; @POST @Path("/json") @@ -41,7 +41,7 @@ public interface UpdateApi { @Operation( summary = "Index documents in JSON format", tags = {"update"}) - SolrJerseyResponse updateJson() throws Exception; + UpdateResponse updateJson() throws Exception; @POST @Path("/xml") @@ -49,7 +49,7 @@ public interface UpdateApi { @Operation( summary = "Index documents in XML format", tags = {"update"}) - SolrJerseyResponse updateXml() throws Exception; + UpdateResponse updateXml() throws Exception; @POST @Path("/csv") @@ -57,7 +57,7 @@ public interface UpdateApi { @Operation( summary = "Index documents in CSV format", tags = {"update"}) - SolrJerseyResponse updateCsv() throws Exception; + UpdateResponse updateCsv() throws Exception; @POST @Path("/javabin") @@ -65,5 +65,5 @@ public interface UpdateApi { @Operation( summary = "Index documents in Javabin format", tags = {"update"}) - SolrJerseyResponse updateJavabin() throws Exception; + UpdateResponse updateJavabin() throws Exception; } diff --git a/solr/api/src/java/org/apache/solr/client/api/model/UpdateResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/UpdateResponse.java new file mode 100644 index 000000000000..f51a00322c33 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/UpdateResponse.java @@ -0,0 +1,37 @@ +/* + * 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.api.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +/** Version information returned by an update request with {@code versions=true}. */ +public class UpdateResponse extends SolrJerseyResponse { + + @JsonProperty("adds") + @Schema(description = "Alternating document IDs and assigned versions for added documents.") + public List adds; + + @JsonProperty("deletes") + @Schema(description = "Alternating document IDs and assigned versions for deleted documents.") + public List deletes; + + @JsonProperty("deleteByQuery") + @Schema(description = "Alternating queries and assigned versions for delete-by-query commands.") + public List deleteByQuery; +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java index 4054c3c465b0..8efe597f38d6 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java @@ -21,10 +21,13 @@ import static org.apache.solr.security.PermissionNameProvider.Name.UPDATE_PERM; import jakarta.inject.Inject; +import java.util.ArrayList; +import java.util.List; import org.apache.solr.api.JerseyResource; import org.apache.solr.client.api.endpoint.UpdateApi; -import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.client.api.model.UpdateResponse; import org.apache.solr.common.SolrException; +import org.apache.solr.common.util.NamedList; import org.apache.solr.handler.UpdateRequestHandler; import org.apache.solr.jersey.APIConfigProvider; import org.apache.solr.jersey.PermissionName; @@ -56,44 +59,58 @@ public UpdateAPI( @Override @PermissionName(UPDATE_PERM) - public SolrJerseyResponse update() throws Exception { + public UpdateResponse update() throws Exception { return handleUpdate(UpdateRequestHandler.DOC_PATH); } @Override @PermissionName(UPDATE_PERM) - public SolrJerseyResponse updateJson() { + public UpdateResponse updateJson() { return handleUpdate(UpdateRequestHandler.DOC_PATH); } @Override @PermissionName(UPDATE_PERM) - public SolrJerseyResponse updateXml() { + public UpdateResponse updateXml() { return handleUpdate(null); } @Override @PermissionName(UPDATE_PERM) - public SolrJerseyResponse updateCsv() { + public UpdateResponse updateCsv() { return handleUpdate(null); } @Override @PermissionName(UPDATE_PERM) - public SolrJerseyResponse updateJavabin() { + public UpdateResponse updateJavabin() { return handleUpdate(UpdateRequestHandler.BIN_PATH); } - private SolrJerseyResponse handleUpdate(String pathOverride) { - final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + private UpdateResponse handleUpdate(String pathOverride) { + final UpdateResponse response = instantiateJerseyResponse(UpdateResponse.class); if (pathOverride != null) { solrQueryRequest.getContext().put(PATH, pathOverride); } updateRequestHandler.handleRequest(solrQueryRequest, solrQueryResponse); rethrowAnyException(solrQueryResponse); + response.adds = takeVersionResults("adds"); + response.deletes = takeVersionResults("deletes"); + response.deleteByQuery = takeVersionResults("deleteByQuery"); return response; } + private List takeVersionResults(String name) { + final NamedList values = (NamedList) solrQueryResponse.getValues().remove(name); + if (values == null) return null; + final List pairs = new ArrayList<>(values.size() * 2); + for (int i = 0; i < values.size(); i++) { + pairs.add(values.getName(i)); + pairs.add(values.getVal(i)); + } + return pairs; + } + private void rethrowAnyException(SolrQueryResponse rsp) { final Exception ex = rsp.getException(); if (ex instanceof SolrException solrEx) throw solrEx; diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java index 8d4c718fb932..0465b1165fb4 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java @@ -20,6 +20,7 @@ import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; import java.io.ByteArrayOutputStream; +import java.util.List; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrRequest; @@ -28,10 +29,14 @@ import org.apache.solr.client.solrj.request.QueryRequest; import org.apache.solr.client.solrj.request.RequestWriter; import org.apache.solr.client.solrj.request.UpdateRequest; +import org.apache.solr.client.solrj.response.JavaBinResponseParser; import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.client.solrj.response.XMLResponseParser; +import org.apache.solr.client.solrj.response.json.JsonMapResponseParser; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.util.EnvUtils; +import org.apache.solr.common.util.NamedList; import org.apache.solr.util.ExternalPaths; import org.apache.solr.util.SolrJettyTestRule; import org.junit.BeforeClass; @@ -134,14 +139,29 @@ public void testUpdateJavabinViaV2Api() throws Exception { doc.setField("id", "v2updatejavabin1"); final UpdateRequest updateRequest = new UpdateRequest(); updateRequest.add(doc); + updateRequest.deleteById("v2deleteversion1"); + updateRequest.deleteByQuery("id:v2deletequery1"); final ByteArrayOutputStream payload = new ByteArrayOutputStream(); new JavaBinUpdateRequestCodec().marshal(updateRequest, payload); + final ModifiableSolrParams params = new ModifiableSolrParams(); + params.set("versions", "true"); final GenericV2SolrRequest addReq = new GenericV2SolrRequest( - SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update/javabin"); + SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update/javabin", params); + addReq.setResponseParser(new JavaBinResponseParser()); addReq.withContent(payload.toByteArray(), "application/javabin"); - client.request(addReq); + final NamedList updateResponse = client.request(addReq); + assertEquals(1, updateResponse.getAll("responseHeader").size()); + final List adds = (List) updateResponse.get("adds"); + assertEquals("v2updatejavabin1", adds.get(0)); + assertTrue(((Number) adds.get(1)).longValue() > 0); + final List deletes = (List) updateResponse.get("deletes"); + assertEquals("v2deleteversion1", deletes.get(0)); + assertTrue(((Number) deletes.get(1)).longValue() < 0); + final List deleteByQuery = (List) updateResponse.get("deleteByQuery"); + assertEquals("id:v2deletequery1", deleteByQuery.get(0)); + assertTrue(((Number) deleteByQuery.get(1)).longValue() < 0); client.commit(CORE_NAME); final ModifiableSolrParams queryParams = new ModifiableSolrParams(); @@ -149,4 +169,43 @@ public void testUpdateJavabinViaV2Api() throws Exception { final QueryResponse queryRsp = new QueryRequest(queryParams).process(client, CORE_NAME); assertEquals(1, queryRsp.getResults().getNumFound()); } + + @Test + public void testUpdateReturnsAssignedVersion() throws Exception { + final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); + final ModifiableSolrParams params = new ModifiableSolrParams(); + params.set("versions", "true"); + final GenericV2SolrRequest addReq = + new GenericV2SolrRequest( + SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update", params); + addReq.setResponseParser(new JsonMapResponseParser()); + addReq.setContentWriter( + new RequestWriter.StringPayloadContentWriter( + "[{\"id\":\"v2version1\"}]", "application/json")); + + final var response = client.request(addReq); + final List adds = (List) response.get("adds"); + assertEquals("v2version1", adds.get(0)); + assertTrue(((Number) adds.get(1)).longValue() > 0); + } + + @Test + public void testXmlUpdateResponseHasOneHeaderAndVersion() throws Exception { + final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); + final ModifiableSolrParams params = new ModifiableSolrParams(); + params.set("versions", "true"); + final GenericV2SolrRequest addReq = + new GenericV2SolrRequest( + SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update/xml", params); + addReq.setResponseParser(new XMLResponseParser()); + addReq.setContentWriter( + new RequestWriter.StringPayloadContentWriter( + "v2xmlversion1", "application/xml")); + + final NamedList response = client.request(addReq); + assertEquals(1, response.getAll("responseHeader").size()); + final List adds = (List) response.get("adds"); + assertEquals("v2xmlversion1", adds.get(0)); + assertTrue(((Number) adds.get(1)).longValue() > 0); + } } From 6455c2e2c6393f41f63f752fb0d37b94b6dfb436 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 08:43:57 -0400 Subject: [PATCH 21/40] add versions to the docs for v2... trying not to get too verbose. --- .../indexing-guide/pages/indexing-with-v2-apis.adoc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc index a1692a8e3b43..66c77fbcffc4 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc @@ -85,6 +85,17 @@ curl -X POST -H 'Content-Type: application/json' \ ---- Query parameters such as `commit`, `commitWithin`, and `overwrite` can be appended to the URL, as shown above. +Add `versions=true` to an update request to include the assigned document versions in the response. +See xref:partial-document-updates.adoc#optimistic-concurrency[Optimistic Concurrency] for more details. + +When `versions=true` is set, the response can include these fields for the operations in the request: + +* `adds`: Alternating document IDs and their assigned versions. +* `deletes`: Alternating document IDs and the versions assigned to their delete operations. +* `deleteByQuery`: Alternating queries and the versions assigned to their delete operations. + +For example, adding a document with ID `1` can return `"adds":["1",123456789]`. +The delete fields apply to formats that accept delete commands, such as XML and Javabin. == XML Document Indexing From 6b32a74c05db6a1c17dba64197c7d43beee940ec Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 08:47:52 -0400 Subject: [PATCH 22/40] Our future is STC, not STCJ4 --- .../org/apache/solr/handler/admin/api/UpdateAPITest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java index 0465b1165fb4..bd2ff40d3f57 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java @@ -21,7 +21,7 @@ import java.io.ByteArrayOutputStream; import java.util.List; -import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.SolrTestCase; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.request.GenericV2SolrRequest; @@ -47,11 +47,11 @@ * Integration tests for the v2 update API endpoints implemented via JAX-RS in {@link * org.apache.solr.handler.admin.api.UpdateAPI}. */ -public class UpdateAPITest extends SolrTestCaseJ4 { +public class UpdateAPITest extends SolrTestCase { @ClassRule public static SolrJettyTestRule solrTestRule = new SolrJettyTestRule(); - private static final String CORE_NAME = DEFAULT_TEST_COLLECTION_NAME; + private static final String CORE_NAME = "update-api-test"; @BeforeClass public static void beforeClass() throws Exception { From cfb1d52c8d693568d8e831a9ee1afaff9f76c0ad Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 09:15:46 -0400 Subject: [PATCH 23/40] Now update parameters show up in annoations. Some magic here on how integrated, so added a comment. --- .../solr/client/api/endpoint/UpdateApi.java | 82 +++++++++++++++++-- .../solr/handler/admin/api/UpdateAPI.java | 34 ++++++-- 2 files changed, 106 insertions(+), 10 deletions(-) diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java index 267e47bf9dc3..ebbab0e40355 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java @@ -19,8 +19,10 @@ import static org.apache.solr.client.api.util.Constants.INDEX_PATH_PREFIX; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; import org.apache.solr.client.api.model.UpdateResponse; import org.apache.solr.client.api.util.StoreApiParameters; @@ -33,7 +35,21 @@ public interface UpdateApi { @Operation( summary = "Index documents using any supported content type", tags = {"update"}) - UpdateResponse update() throws Exception; + UpdateResponse update( + @Parameter(description = "Commit the update immediately") @QueryParam("commit") + Boolean commit, + @Parameter(description = "Commit the update within this many milliseconds") + @QueryParam("commitWithin") + Integer commitWithin, + @Parameter(description = "Overwrite documents with the same unique key") + @QueryParam("overwrite") + Boolean overwrite, + @Parameter(description = "Perform a soft commit") @QueryParam("softCommit") + Boolean softCommit, + @Parameter(description = "Include assigned document versions in the response") + @QueryParam("versions") + Boolean versions) + throws Exception; @POST @Path("/json") @@ -41,7 +57,21 @@ public interface UpdateApi { @Operation( summary = "Index documents in JSON format", tags = {"update"}) - UpdateResponse updateJson() throws Exception; + UpdateResponse updateJson( + @Parameter(description = "Commit the update immediately") @QueryParam("commit") + Boolean commit, + @Parameter(description = "Commit the update within this many milliseconds") + @QueryParam("commitWithin") + Integer commitWithin, + @Parameter(description = "Overwrite documents with the same unique key") + @QueryParam("overwrite") + Boolean overwrite, + @Parameter(description = "Perform a soft commit") @QueryParam("softCommit") + Boolean softCommit, + @Parameter(description = "Include assigned document versions in the response") + @QueryParam("versions") + Boolean versions) + throws Exception; @POST @Path("/xml") @@ -49,7 +79,21 @@ public interface UpdateApi { @Operation( summary = "Index documents in XML format", tags = {"update"}) - UpdateResponse updateXml() throws Exception; + UpdateResponse updateXml( + @Parameter(description = "Commit the update immediately") @QueryParam("commit") + Boolean commit, + @Parameter(description = "Commit the update within this many milliseconds") + @QueryParam("commitWithin") + Integer commitWithin, + @Parameter(description = "Overwrite documents with the same unique key") + @QueryParam("overwrite") + Boolean overwrite, + @Parameter(description = "Perform a soft commit") @QueryParam("softCommit") + Boolean softCommit, + @Parameter(description = "Include assigned document versions in the response") + @QueryParam("versions") + Boolean versions) + throws Exception; @POST @Path("/csv") @@ -57,7 +101,21 @@ public interface UpdateApi { @Operation( summary = "Index documents in CSV format", tags = {"update"}) - UpdateResponse updateCsv() throws Exception; + UpdateResponse updateCsv( + @Parameter(description = "Commit the update immediately") @QueryParam("commit") + Boolean commit, + @Parameter(description = "Commit the update within this many milliseconds") + @QueryParam("commitWithin") + Integer commitWithin, + @Parameter(description = "Overwrite documents with the same unique key") + @QueryParam("overwrite") + Boolean overwrite, + @Parameter(description = "Perform a soft commit") @QueryParam("softCommit") + Boolean softCommit, + @Parameter(description = "Include assigned document versions in the response") + @QueryParam("versions") + Boolean versions) + throws Exception; @POST @Path("/javabin") @@ -65,5 +123,19 @@ public interface UpdateApi { @Operation( summary = "Index documents in Javabin format", tags = {"update"}) - UpdateResponse updateJavabin() throws Exception; + UpdateResponse updateJavabin( + @Parameter(description = "Commit the update immediately") @QueryParam("commit") + Boolean commit, + @Parameter(description = "Commit the update within this many milliseconds") + @QueryParam("commitWithin") + Integer commitWithin, + @Parameter(description = "Overwrite documents with the same unique key") + @QueryParam("overwrite") + Boolean overwrite, + @Parameter(description = "Perform a soft commit") @QueryParam("softCommit") + Boolean softCommit, + @Parameter(description = "Include assigned document versions in the response") + @QueryParam("versions") + Boolean versions) + throws Exception; } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java index 8efe597f38d6..c05182240c12 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java @@ -57,33 +57,57 @@ public UpdateAPI( this.solrQueryResponse = solrQueryResponse; } + // Query parameters like commit, overwrite, etc are declared as method arguments for the JAX-RS/OpenAPI contract + // and via magic are read in by the handler. @Override @PermissionName(UPDATE_PERM) - public UpdateResponse update() throws Exception { + public UpdateResponse update( + Boolean commit, Integer commitWithin, Boolean overwrite, Boolean softCommit, Boolean versions) + throws Exception { return handleUpdate(UpdateRequestHandler.DOC_PATH); } @Override @PermissionName(UPDATE_PERM) - public UpdateResponse updateJson() { + public UpdateResponse updateJson( + Boolean commit, + Integer commitWithin, + Boolean overwrite, + Boolean softCommit, + Boolean versions) { return handleUpdate(UpdateRequestHandler.DOC_PATH); } @Override @PermissionName(UPDATE_PERM) - public UpdateResponse updateXml() { + public UpdateResponse updateXml( + Boolean commit, + Integer commitWithin, + Boolean overwrite, + Boolean softCommit, + Boolean versions) { return handleUpdate(null); } @Override @PermissionName(UPDATE_PERM) - public UpdateResponse updateCsv() { + public UpdateResponse updateCsv( + Boolean commit, + Integer commitWithin, + Boolean overwrite, + Boolean softCommit, + Boolean versions) { return handleUpdate(null); } @Override @PermissionName(UPDATE_PERM) - public UpdateResponse updateJavabin() { + public UpdateResponse updateJavabin( + Boolean commit, + Integer commitWithin, + Boolean overwrite, + Boolean softCommit, + Boolean versions) { return handleUpdate(UpdateRequestHandler.BIN_PATH); } From d11876fe75094e897902e61273d4cb81f5cc7b8c Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 09:32:00 -0400 Subject: [PATCH 24/40] restore the /update end point being the root. will make v1 to v2 docs and conversion simpler. --- .../solr/client/api/endpoint/UpdateApi.java | 13 +++++- .../solr/handler/admin/api/UpdateAPI.java | 6 +-- .../solr/handler/admin/api/UpdateAPITest.java | 44 ++++++++++++++++++- .../pages/indexing-with-v2-apis.adoc | 32 +++++++------- 4 files changed, 74 insertions(+), 21 deletions(-) diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java index ebbab0e40355..8c0856984f3e 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java @@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; @@ -31,9 +32,19 @@ public interface UpdateApi { @POST + @Consumes({ + "application/json", + "text/json", + "application/xml", + "text/xml", + "application/csv", + "text/csv", + "application/javabin", + "application/cbor" + }) @StoreApiParameters @Operation( - summary = "Index documents using any supported content type", + summary = "Send updates using any supported content type", tags = {"update"}) UpdateResponse update( @Parameter(description = "Commit the update immediately") @QueryParam("commit") diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java index c05182240c12..a20c99f03502 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java @@ -57,14 +57,14 @@ public UpdateAPI( this.solrQueryResponse = solrQueryResponse; } - // Query parameters like commit, overwrite, etc are declared as method arguments for the JAX-RS/OpenAPI contract - // and via magic are read in by the handler. + // Query parameters like commit, overwrite, etc are declared as method arguments for the + // JAX-RS/OpenAPI contract and via magic are read in by the handler. @Override @PermissionName(UPDATE_PERM) public UpdateResponse update( Boolean commit, Integer commitWithin, Boolean overwrite, Boolean softCommit, Boolean versions) throws Exception { - return handleUpdate(UpdateRequestHandler.DOC_PATH); + return handleUpdate(null); } @Override diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java index bd2ff40d3f57..b2500def96a2 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java @@ -68,12 +68,13 @@ public static void beforeClass() throws Exception { public void testUpdateViaV2Api() throws Exception { final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); - // POST a JSON array of documents via the V2 /update endpoint (rewrites to /update/json/docs) + // The generic /update endpoint selects the update-command loader from Content-Type. final GenericV2SolrRequest addReq = new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); addReq.setContentWriter( new RequestWriter.StringPayloadContentWriter( - "[{\"id\":\"v2update1\",\"title\":\"V2 update test\"}]", "application/json")); + "{\"add\":{\"doc\":{\"id\":\"v2update1\",\"title\":\"V2 update test\"}}}", + "application/json")); client.request(addReq); // Commit via standard SolrJ commit (v2 /update is docs-only and does not support commands) @@ -86,6 +87,45 @@ public void testUpdateViaV2Api() throws Exception { assertEquals(1, queryRsp.getResults().getNumFound()); } + @Test + public void testGenericUpdateSelectsXmlFromContentType() throws Exception { + final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); + final GenericV2SolrRequest addReq = + new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); + addReq.setContentWriter( + new RequestWriter.StringPayloadContentWriter( + "v2genericxml1", "application/xml")); + client.request(addReq); + client.commit(CORE_NAME); + + final ModifiableSolrParams queryParams = new ModifiableSolrParams(); + queryParams.set("q", "id:v2genericxml1"); + final QueryResponse queryRsp = new QueryRequest(queryParams).process(client, CORE_NAME); + assertEquals(1, queryRsp.getResults().getNumFound()); + } + + @Test + public void testGenericUpdateSelectsJavabinFromContentType() throws Exception { + final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); + final SolrInputDocument doc = new SolrInputDocument(); + doc.setField("id", "v2genericjavabin1"); + final UpdateRequest updateRequest = new UpdateRequest(); + updateRequest.add(doc); + final ByteArrayOutputStream payload = new ByteArrayOutputStream(); + new JavaBinUpdateRequestCodec().marshal(updateRequest, payload); + + final GenericV2SolrRequest addReq = + new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); + addReq.withContent(payload.toByteArray(), "application/javabin"); + client.request(addReq); + client.commit(CORE_NAME); + + final ModifiableSolrParams queryParams = new ModifiableSolrParams(); + queryParams.set("q", "id:v2genericjavabin1"); + final QueryResponse queryRsp = new QueryRequest(queryParams).process(client, CORE_NAME); + assertEquals(1, queryRsp.getResults().getNumFound()); + } + @Test public void testUpdateJsonViaV2Api() throws Exception { final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc index 66c77fbcffc4..51aaac9cde9c 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc @@ -40,24 +40,26 @@ The following sub-paths are available: [width="100%",options="header",] |=== |Path |Accepted Format |Notes -|`/update` |JSON document or array of documents |Equivalent to v1 `/update/json/docs`; documents only (no JSON command syntax) -|`/update/json` |JSON document or array of documents |Same behavior as `/update`; documents only (no JSON command syntax) +|`/update` |JSON, XML, CSV, Javabin, or CBOR |Selects the update loader from the request `Content-Type`, like v1 `/update` +|`/update/json` |JSON document or array of documents |Equivalent to v1 `/update/json/docs`; documents only (no JSON command syntax) |`/update/xml` |XML |Supports full XML update syntax (add, delete, commit, optimize) |`/update/csv` |CSV |Equivalent to v1 `/update/csv` |`/update/javabin` |Javabin |Equivalent to v1 `/update` with `Content-Type: application/javabin` |=== -IMPORTANT: The v2 `/update` and `/update/json` endpoints are document-only: they process one or more JSON documents (either a single JSON object or an array of objects, like the v1 `/update/json/docs` path) and do *not* support the full xref:indexing-with-update-handlers.adoc#sending-json-update-commands[JSON update command syntax] (commit, delete, optimize within the request body). -Use `/update/xml` for those operations, or issue commit/rollback via separate API calls. +The v2 `/update` endpoint is the general-purpose update endpoint and selects a loader based on the request `Content-Type`. +When it receives JSON, it supports the full xref:indexing-with-update-handlers.adoc#sending-json-update-commands[JSON update command syntax], like v1 `/update`. + +IMPORTANT: The v2 `/update/json` endpoint is document-only: it processes one or more JSON documents (either a single JSON object or an array of objects, like the v1 `/update/json/docs` path) and does *not* support JSON update commands such as commit, delete, or optimize in the request body. == JSON Document Indexing -The v2 `/update` and `/update/json` endpoints both accept JSON document(s). The example below shows an array of documents: +The v2 `/update/json` endpoint accepts JSON document(s). The example below shows an array of documents: [source,bash] ---- curl -X POST -H 'Content-Type: application/json' \ - 'http://localhost:8983/api/collections/my_collection/update?commit=true' \ + 'http://localhost:8983/api/collections/my_collection/update/json?commit=true' \ --data-binary ' [ { @@ -76,7 +78,7 @@ A single document can also be posted as a JSON object: [source,bash] ---- curl -X POST -H 'Content-Type: application/json' \ - 'http://localhost:8983/api/collections/my_collection/update?commit=true' \ + 'http://localhost:8983/api/collections/my_collection/update/json?commit=true' \ --data-binary ' { "id": "1", @@ -148,13 +150,13 @@ This endpoint is primarily intended for use by SolrJ clients. == Commit and Rollback -Because the v2 `/update` and `/update/json` endpoints do not support update commands in the request body, commits and rollbacks must be issued separately. +Because the v2 `/update/json` endpoint does not support update commands in the request body, commits and rollbacks must be issued separately. Use the `commit=true` or `commitWithin=N` URL parameters to commit after indexing: [source,bash] ---- curl -X POST -H 'Content-Type: application/json' \ - 'http://localhost:8983/api/collections/my_collection/update?commit=true' \ + 'http://localhost:8983/api/collections/my_collection/update/json?commit=true' \ --data-binary '[{"id":"1","title":"Doc 1"}]' ---- @@ -163,7 +165,7 @@ For a soft commit: [source,bash] ---- curl -X POST -H 'Content-Type: application/json' \ - 'http://localhost:8983/api/collections/my_collection/update?softCommit=true' \ + 'http://localhost:8983/api/collections/my_collection/update/json?softCommit=true' \ --data-binary '[{"id":"1","title":"Doc 1"}]' ---- @@ -184,11 +186,11 @@ The table below summarizes the key differences between the v1 and v2 update endp |=== |Feature |V1 |V2 |Base path |`/solr/{collection}/update` |`/api/collections/{collection}/update` -|JSON documents |`/update` or `/update/json/docs` |`/update` or `/update/json` -|JSON update commands (commit/delete/optimize) |`/update` |Not supported via `/update` or `/update/json`; use `/update/xml` or URL params -|XML updates |`/update` (via Content-Type) |`/update/xml` -|CSV updates |`/update/csv` |`/update/csv` -|Javabin updates |`/update` (via Content-Type) |`/update/javabin` +|JSON documents |`/update` or `/update/json/docs` |`/update` with JSON update syntax, or `/update/json` for bare documents +|JSON update commands (commit/delete/optimize) |`/update` |`/update` +|XML updates |`/update` (via Content-Type) |`/update` (via Content-Type) or `/update/xml` +|CSV updates |`/update/csv` |`/update` (via Content-Type) or `/update/csv` +|Javabin updates |`/update` (via Content-Type) |`/update` (via Content-Type) or `/update/javabin` |=== For full details on the v1 update handlers, see xref:indexing-with-update-handlers.adoc[]. From 162511f467c1eb31bad8adc142d17166ae2d414d Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 09:43:24 -0400 Subject: [PATCH 25/40] Rework test to focus on compat between V1 and V2 --- .../solr/handler/admin/api/UpdateAPITest.java | 157 ++++++++++++------ 1 file changed, 102 insertions(+), 55 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java index b2500def96a2..f4471ddeff86 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java @@ -20,10 +20,13 @@ import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Locale; import org.apache.solr.SolrTestCase; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrRequest; +import org.apache.solr.client.solrj.request.GenericSolrRequest; import org.apache.solr.client.solrj.request.GenericV2SolrRequest; import org.apache.solr.client.solrj.request.JavaBinUpdateRequestCodec; import org.apache.solr.client.solrj.request.QueryRequest; @@ -65,65 +68,21 @@ public static void beforeClass() throws Exception { } @Test - public void testUpdateViaV2Api() throws Exception { + public void testV1AndV2GenericUpdateParityAcrossFormats() throws Exception { final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); - // The generic /update endpoint selects the update-command loader from Content-Type. - final GenericV2SolrRequest addReq = - new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); - addReq.setContentWriter( - new RequestWriter.StringPayloadContentWriter( - "{\"add\":{\"doc\":{\"id\":\"v2update1\",\"title\":\"V2 update test\"}}}", - "application/json")); - client.request(addReq); - - // Commit via standard SolrJ commit (v2 /update is docs-only and does not support commands) - client.commit(CORE_NAME); - - // Verify the document was indexed - final ModifiableSolrParams queryParams = new ModifiableSolrParams(); - queryParams.set("q", "id:v2update1"); - final QueryResponse queryRsp = new QueryRequest(queryParams).process(client, CORE_NAME); - assertEquals(1, queryRsp.getResults().getNumFound()); - } - - @Test - public void testGenericUpdateSelectsXmlFromContentType() throws Exception { - final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); - final GenericV2SolrRequest addReq = - new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); - addReq.setContentWriter( - new RequestWriter.StringPayloadContentWriter( - "v2genericxml1", "application/xml")); - client.request(addReq); - client.commit(CORE_NAME); - - final ModifiableSolrParams queryParams = new ModifiableSolrParams(); - queryParams.set("q", "id:v2genericxml1"); - final QueryResponse queryRsp = new QueryRequest(queryParams).process(client, CORE_NAME); - assertEquals(1, queryRsp.getResults().getNumFound()); - } - - @Test - public void testGenericUpdateSelectsJavabinFromContentType() throws Exception { - final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); - final SolrInputDocument doc = new SolrInputDocument(); - doc.setField("id", "v2genericjavabin1"); - final UpdateRequest updateRequest = new UpdateRequest(); - updateRequest.add(doc); - final ByteArrayOutputStream payload = new ByteArrayOutputStream(); - new JavaBinUpdateRequestCodec().marshal(updateRequest, payload); + for (UpdateFormat format : UpdateFormat.values()) { + final String v1Id = "parity-v1-" + format.name().toLowerCase(Locale.ROOT); + final String v2Id = "parity-v2-" + format.name().toLowerCase(Locale.ROOT); - final GenericV2SolrRequest addReq = - new GenericV2SolrRequest(SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update"); - addReq.withContent(payload.toByteArray(), "application/javabin"); - client.request(addReq); - client.commit(CORE_NAME); + final NamedList v1Response = sendV1Update(client, format, v1Id); + final NamedList v2Response = sendV2Update(client, format, v2Id); - final ModifiableSolrParams queryParams = new ModifiableSolrParams(); - queryParams.set("q", "id:v2genericjavabin1"); - final QueryResponse queryRsp = new QueryRequest(queryParams).process(client, CORE_NAME); - assertEquals(1, queryRsp.getResults().getNumFound()); + assertSuccessfulAdd(format, v1Id, v1Response); + assertSuccessfulAdd(format, v2Id, v2Response); + assertIndexed(client, v1Id); + assertIndexed(client, v2Id); + } } @Test @@ -248,4 +207,92 @@ public void testXmlUpdateResponseHasOneHeaderAndVersion() throws Exception { assertEquals("v2xmlversion1", adds.get(0)); assertTrue(((Number) adds.get(1)).longValue() > 0); } + + private static NamedList sendV1Update(SolrClient client, UpdateFormat format, String id) + throws Exception { + final GenericSolrRequest request = + new GenericSolrRequest(SolrRequest.METHOD.POST, "/update", updateParams()); + request.setRequiresCollection(true); + request.setResponseParser(new JsonMapResponseParser()); + request.withContent(format.payload(id), format.contentType); + return client.request(request, CORE_NAME); + } + + private static NamedList sendV2Update(SolrClient client, UpdateFormat format, String id) + throws Exception { + final GenericV2SolrRequest request = + new GenericV2SolrRequest( + SolrRequest.METHOD.POST, "/cores/" + CORE_NAME + "/update", updateParams()); + request.setResponseParser(new JsonMapResponseParser()); + request.withContent(format.payload(id), format.contentType); + return client.request(request); + } + + private static ModifiableSolrParams updateParams() { + final ModifiableSolrParams params = new ModifiableSolrParams(); + params.set("versions", true); + params.set("commit", true); + return params; + } + + private static void assertSuccessfulAdd( + UpdateFormat format, String expectedId, NamedList response) { + assertEquals(format.name(), 1, response.getAll("responseHeader").size()); + final List adds = (List) response.get("adds"); + assertNotNull(format.name(), adds); + assertEquals(format.name(), expectedId, adds.get(0)); + assertTrue(format.name(), ((Number) adds.get(1)).longValue() > 0); + } + + private static void assertIndexed(SolrClient client, String id) throws Exception { + final ModifiableSolrParams queryParams = new ModifiableSolrParams(); + queryParams.set("q", "id:" + id); + final QueryResponse queryResponse = new QueryRequest(queryParams).process(client, CORE_NAME); + assertEquals(id, 1, queryResponse.getResults().getNumFound()); + } + + private enum UpdateFormat { + JSON("application/json") { + @Override + byte[] payload(String id) { + return bytes("{\"add\":{\"doc\":{\"id\":\"" + id + "\"}}}"); + } + }, + XML("application/xml") { + @Override + byte[] payload(String id) { + return bytes("" + id + ""); + } + }, + CSV("application/csv") { + @Override + byte[] payload(String id) { + return bytes("id\n" + id + "\n"); + } + }, + JAVABIN("application/javabin") { + @Override + byte[] payload(String id) throws Exception { + final SolrInputDocument doc = new SolrInputDocument(); + doc.setField("id", id); + final UpdateRequest updateRequest = new UpdateRequest(); + updateRequest.add(doc); + final ByteArrayOutputStream payload = new ByteArrayOutputStream(); + new JavaBinUpdateRequestCodec().marshal(updateRequest, payload); + return payload.toByteArray(); + } + }; + + private final String contentType; + + UpdateFormat(String contentType) { + this.contentType = contentType; + } + + abstract byte[] payload(String id) throws Exception; + + static byte[] bytes(String value) { + return value.getBytes(StandardCharsets.UTF_8); + } + } } From d904befa2c0ec39417e35d9912ec862cec6af418 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 11:12:04 -0400 Subject: [PATCH 26/40] Strong enough typing enabled to use generate V2 api in Solr Admin --- .../solr/client/api/endpoint/UpdateApi.java | 69 +++++++++++++++++-- .../solr/client/api/model/UpdateResponse.java | 12 ++-- .../client/api/model/VersionedDocument.java | 32 +++++++++ .../solr/client/api/model/VersionedQuery.java | 32 +++++++++ .../solr/handler/admin/api/UpdateAPI.java | 65 +++++++++++++---- .../solr/handler/admin/api/UpdateAPITest.java | 58 +++++++++++----- .../pages/indexing-with-v2-apis.adoc | 23 +++++-- .../web/js/angular/controllers/documents.js | 38 +++++++++- solr/webapp/web/js/angular/services.js | 6 ++ 9 files changed, 286 insertions(+), 49 deletions(-) create mode 100644 solr/api/src/java/org/apache/solr/client/api/model/VersionedDocument.java create mode 100644 solr/api/src/java/org/apache/solr/client/api/model/VersionedQuery.java diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java index 8c0856984f3e..b798fc4ed1bd 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java @@ -16,14 +16,19 @@ */ package org.apache.solr.client.api.endpoint; +import static org.apache.solr.client.api.util.Constants.GENERIC_ENTITY_PROPERTY; import static org.apache.solr.client.api.util.Constants.INDEX_PATH_PREFIX; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.extensions.Extension; +import io.swagger.v3.oas.annotations.extensions.ExtensionProperty; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; +import java.io.InputStream; import org.apache.solr.client.api.model.UpdateResponse; import org.apache.solr.client.api.util.StoreApiParameters; @@ -59,11 +64,22 @@ UpdateResponse update( Boolean softCommit, @Parameter(description = "Include assigned document versions in the response") @QueryParam("versions") - Boolean versions) + Boolean versions, + @Parameter(required = true) + @RequestBody( + required = true, + description = "Update content in the format selected by the Content-Type header.", + extensions = + @Extension( + properties = { + @ExtensionProperty(name = GENERIC_ENTITY_PROPERTY, value = "true") + })) + InputStream requestBody) throws Exception; @POST @Path("/json") + @Consumes({"application/json", "text/json"}) @StoreApiParameters @Operation( summary = "Index documents in JSON format", @@ -81,11 +97,22 @@ UpdateResponse updateJson( Boolean softCommit, @Parameter(description = "Include assigned document versions in the response") @QueryParam("versions") - Boolean versions) + Boolean versions, + @Parameter(required = true) + @RequestBody( + required = true, + description = "JSON update content.", + extensions = + @Extension( + properties = { + @ExtensionProperty(name = GENERIC_ENTITY_PROPERTY, value = "true") + })) + InputStream requestBody) throws Exception; @POST @Path("/xml") + @Consumes({"application/xml", "text/xml"}) @StoreApiParameters @Operation( summary = "Index documents in XML format", @@ -103,11 +130,22 @@ UpdateResponse updateXml( Boolean softCommit, @Parameter(description = "Include assigned document versions in the response") @QueryParam("versions") - Boolean versions) + Boolean versions, + @Parameter(required = true) + @RequestBody( + required = true, + description = "XML update content.", + extensions = + @Extension( + properties = { + @ExtensionProperty(name = GENERIC_ENTITY_PROPERTY, value = "true") + })) + InputStream requestBody) throws Exception; @POST @Path("/csv") + @Consumes({"application/csv", "text/csv"}) @StoreApiParameters @Operation( summary = "Index documents in CSV format", @@ -125,11 +163,22 @@ UpdateResponse updateCsv( Boolean softCommit, @Parameter(description = "Include assigned document versions in the response") @QueryParam("versions") - Boolean versions) + Boolean versions, + @Parameter(required = true) + @RequestBody( + required = true, + description = "CSV update content.", + extensions = + @Extension( + properties = { + @ExtensionProperty(name = GENERIC_ENTITY_PROPERTY, value = "true") + })) + InputStream requestBody) throws Exception; @POST @Path("/javabin") + @Consumes("application/javabin") @StoreApiParameters @Operation( summary = "Index documents in Javabin format", @@ -147,6 +196,16 @@ UpdateResponse updateJavabin( Boolean softCommit, @Parameter(description = "Include assigned document versions in the response") @QueryParam("versions") - Boolean versions) + Boolean versions, + @Parameter(required = true) + @RequestBody( + required = true, + description = "Javabin update content.", + extensions = + @Extension( + properties = { + @ExtensionProperty(name = GENERIC_ENTITY_PROPERTY, value = "true") + })) + InputStream requestBody) throws Exception; } diff --git a/solr/api/src/java/org/apache/solr/client/api/model/UpdateResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/UpdateResponse.java index f51a00322c33..9ed26bb870d2 100644 --- a/solr/api/src/java/org/apache/solr/client/api/model/UpdateResponse.java +++ b/solr/api/src/java/org/apache/solr/client/api/model/UpdateResponse.java @@ -24,14 +24,14 @@ public class UpdateResponse extends SolrJerseyResponse { @JsonProperty("adds") - @Schema(description = "Alternating document IDs and assigned versions for added documents.") - public List adds; + @Schema(description = "Documents added and the versions assigned to them.") + public List adds; @JsonProperty("deletes") - @Schema(description = "Alternating document IDs and assigned versions for deleted documents.") - public List deletes; + @Schema(description = "Documents deleted and the versions assigned to their delete operations.") + public List deletes; @JsonProperty("deleteByQuery") - @Schema(description = "Alternating queries and assigned versions for delete-by-query commands.") - public List deleteByQuery; + @Schema(description = "Delete-by-query operations and the versions assigned to them.") + public List deleteByQuery; } diff --git a/solr/api/src/java/org/apache/solr/client/api/model/VersionedDocument.java b/solr/api/src/java/org/apache/solr/client/api/model/VersionedDocument.java new file mode 100644 index 000000000000..230dacda77bd --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/VersionedDocument.java @@ -0,0 +1,32 @@ +/* + * 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.api.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +/** A document identifier and the version assigned to an update operation. */ +public class VersionedDocument { + + @JsonProperty("id") + @Schema(description = "The document's unique identifier.") + public String id; + + @JsonProperty("version") + @Schema(description = "The version assigned to the operation.") + public long version; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/VersionedQuery.java b/solr/api/src/java/org/apache/solr/client/api/model/VersionedQuery.java new file mode 100644 index 000000000000..9eee66c69bd7 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/VersionedQuery.java @@ -0,0 +1,32 @@ +/* + * 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.api.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +/** A delete-by-query expression and the version assigned to the operation. */ +public class VersionedQuery { + + @JsonProperty("query") + @Schema(description = "The delete-by-query expression.") + public String query; + + @JsonProperty("version") + @Schema(description = "The version assigned to the operation.") + public long version; +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java index a20c99f03502..5ff2ddb60787 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java @@ -21,13 +21,17 @@ import static org.apache.solr.security.PermissionNameProvider.Name.UPDATE_PERM; import jakarta.inject.Inject; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.apache.solr.api.JerseyResource; import org.apache.solr.client.api.endpoint.UpdateApi; import org.apache.solr.client.api.model.UpdateResponse; +import org.apache.solr.client.api.model.VersionedDocument; +import org.apache.solr.client.api.model.VersionedQuery; import org.apache.solr.common.SolrException; import org.apache.solr.common.util.NamedList; +import org.apache.solr.core.SolrCore; import org.apache.solr.handler.UpdateRequestHandler; import org.apache.solr.jersey.APIConfigProvider; import org.apache.solr.jersey.PermissionName; @@ -62,7 +66,12 @@ public UpdateAPI( @Override @PermissionName(UPDATE_PERM) public UpdateResponse update( - Boolean commit, Integer commitWithin, Boolean overwrite, Boolean softCommit, Boolean versions) + Boolean commit, + Integer commitWithin, + Boolean overwrite, + Boolean softCommit, + Boolean versions, + InputStream requestBody) throws Exception { return handleUpdate(null); } @@ -74,7 +83,8 @@ public UpdateResponse updateJson( Integer commitWithin, Boolean overwrite, Boolean softCommit, - Boolean versions) { + Boolean versions, + InputStream requestBody) { return handleUpdate(UpdateRequestHandler.DOC_PATH); } @@ -85,7 +95,8 @@ public UpdateResponse updateXml( Integer commitWithin, Boolean overwrite, Boolean softCommit, - Boolean versions) { + Boolean versions, + InputStream requestBody) { return handleUpdate(null); } @@ -96,7 +107,8 @@ public UpdateResponse updateCsv( Integer commitWithin, Boolean overwrite, Boolean softCommit, - Boolean versions) { + Boolean versions, + InputStream requestBody) { return handleUpdate(null); } @@ -107,7 +119,8 @@ public UpdateResponse updateJavabin( Integer commitWithin, Boolean overwrite, Boolean softCommit, - Boolean versions) { + Boolean versions, + InputStream requestBody) { return handleUpdate(UpdateRequestHandler.BIN_PATH); } @@ -116,23 +129,47 @@ private UpdateResponse handleUpdate(String pathOverride) { if (pathOverride != null) { solrQueryRequest.getContext().put(PATH, pathOverride); } - updateRequestHandler.handleRequest(solrQueryRequest, solrQueryResponse); + // The distributed update processor writes replication metadata into the legacy response + // header while handling the request. Initialize it for the handler, then leave serialization + // to the typed Jersey response so only one responseHeader is returned to the client. + SolrCore.preDecorateResponse(solrQueryRequest, solrQueryResponse); + try { + updateRequestHandler.handleRequest(solrQueryRequest, solrQueryResponse); + } finally { + solrQueryResponse.getValues().remove("responseHeader"); + } rethrowAnyException(solrQueryResponse); - response.adds = takeVersionResults("adds"); - response.deletes = takeVersionResults("deletes"); - response.deleteByQuery = takeVersionResults("deleteByQuery"); + response.adds = takeDocumentVersionResults("adds"); + response.deletes = takeDocumentVersionResults("deletes"); + response.deleteByQuery = takeQueryVersionResults(); return response; } - private List takeVersionResults(String name) { + private List takeDocumentVersionResults(String name) { final NamedList values = (NamedList) solrQueryResponse.getValues().remove(name); if (values == null) return null; - final List pairs = new ArrayList<>(values.size() * 2); + final List results = new ArrayList<>(values.size()); + for (int i = 0; i < values.size(); i++) { + final VersionedDocument result = new VersionedDocument(); + result.id = values.getName(i); + result.version = ((Number) values.getVal(i)).longValue(); + results.add(result); + } + return results; + } + + private List takeQueryVersionResults() { + final NamedList values = + (NamedList) solrQueryResponse.getValues().remove("deleteByQuery"); + if (values == null) return null; + final List results = new ArrayList<>(values.size()); for (int i = 0; i < values.size(); i++) { - pairs.add(values.getName(i)); - pairs.add(values.getVal(i)); + final VersionedQuery result = new VersionedQuery(); + result.query = values.getName(i); + result.version = ((Number) values.getVal(i)).longValue(); + results.add(result); } - return pairs; + return results; } private void rethrowAnyException(SolrQueryResponse rsp) { diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java index f4471ddeff86..88d43e2ee1a8 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java @@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Locale; +import java.util.Map; import org.apache.solr.SolrTestCase; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrRequest; @@ -78,8 +79,8 @@ public void testV1AndV2GenericUpdateParityAcrossFormats() throws Exception { final NamedList v1Response = sendV1Update(client, format, v1Id); final NamedList v2Response = sendV2Update(client, format, v2Id); - assertSuccessfulAdd(format, v1Id, v1Response); - assertSuccessfulAdd(format, v2Id, v2Response); + assertLegacySuccessfulAdd(format, v1Id, v1Response); + assertTypedSuccessfulAdd(format, v2Id, v2Response); assertIndexed(client, v1Id); assertIndexed(client, v2Id); } @@ -152,15 +153,9 @@ public void testUpdateJavabinViaV2Api() throws Exception { addReq.withContent(payload.toByteArray(), "application/javabin"); final NamedList updateResponse = client.request(addReq); assertEquals(1, updateResponse.getAll("responseHeader").size()); - final List adds = (List) updateResponse.get("adds"); - assertEquals("v2updatejavabin1", adds.get(0)); - assertTrue(((Number) adds.get(1)).longValue() > 0); - final List deletes = (List) updateResponse.get("deletes"); - assertEquals("v2deleteversion1", deletes.get(0)); - assertTrue(((Number) deletes.get(1)).longValue() < 0); - final List deleteByQuery = (List) updateResponse.get("deleteByQuery"); - assertEquals("id:v2deletequery1", deleteByQuery.get(0)); - assertTrue(((Number) deleteByQuery.get(1)).longValue() < 0); + assertTypedVersion(updateResponse, "adds", "id", "v2updatejavabin1", true); + assertTypedVersion(updateResponse, "deletes", "id", "v2deleteversion1", false); + assertTypedVersion(updateResponse, "deleteByQuery", "query", "id:v2deletequery1", false); client.commit(CORE_NAME); final ModifiableSolrParams queryParams = new ModifiableSolrParams(); @@ -183,9 +178,7 @@ public void testUpdateReturnsAssignedVersion() throws Exception { "[{\"id\":\"v2version1\"}]", "application/json")); final var response = client.request(addReq); - final List adds = (List) response.get("adds"); - assertEquals("v2version1", adds.get(0)); - assertTrue(((Number) adds.get(1)).longValue() > 0); + assertTypedVersion(response, "adds", "id", "v2version1", true); } @Test @@ -203,9 +196,7 @@ public void testXmlUpdateResponseHasOneHeaderAndVersion() throws Exception { final NamedList response = client.request(addReq); assertEquals(1, response.getAll("responseHeader").size()); - final List adds = (List) response.get("adds"); - assertEquals("v2xmlversion1", adds.get(0)); - assertTrue(((Number) adds.get(1)).longValue() > 0); + assertTypedVersion(response, "adds", "id", "v2xmlversion1", true); } private static NamedList sendV1Update(SolrClient client, UpdateFormat format, String id) @@ -235,7 +226,7 @@ private static ModifiableSolrParams updateParams() { return params; } - private static void assertSuccessfulAdd( + private static void assertLegacySuccessfulAdd( UpdateFormat format, String expectedId, NamedList response) { assertEquals(format.name(), 1, response.getAll("responseHeader").size()); final List adds = (List) response.get("adds"); @@ -244,6 +235,37 @@ private static void assertSuccessfulAdd( assertTrue(format.name(), ((Number) adds.get(1)).longValue() > 0); } + private static void assertTypedSuccessfulAdd( + UpdateFormat format, String expectedId, NamedList response) { + assertEquals(format.name(), 1, response.getAll("responseHeader").size()); + assertTypedVersion(response, "adds", "id", expectedId, true); + } + + private static void assertTypedVersion( + NamedList response, + String field, + String key, + String expectedValue, + boolean positiveVersion) { + final List values = (List) response.get(field); + assertNotNull(field, values); + assertEquals(1, values.size()); + final Object value = values.get(0); + final Object actualValue; + final Object version; + if (value instanceof Map map) { + actualValue = map.get(key); + version = map.get("version"); + } else { + final NamedList namedValue = (NamedList) value; + actualValue = namedValue.get(key); + version = namedValue.get("version"); + } + assertEquals(expectedValue, actualValue); + final long numericVersion = ((Number) version).longValue(); + assertTrue(positiveVersion ? numericVersion > 0 : numericVersion < 0); + } + private static void assertIndexed(SolrClient client, String id) throws Exception { final ModifiableSolrParams queryParams = new ModifiableSolrParams(); queryParams.set("q", "id:" + id); diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc index 51aaac9cde9c..ede7485cf1f6 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc @@ -92,12 +92,27 @@ See xref:partial-document-updates.adoc#optimistic-concurrency[Optimistic Concurr When `versions=true` is set, the response can include these fields for the operations in the request: -* `adds`: Alternating document IDs and their assigned versions. -* `deletes`: Alternating document IDs and the versions assigned to their delete operations. -* `deleteByQuery`: Alternating queries and the versions assigned to their delete operations. +* `adds`: Documents added and the versions assigned to them. +* `deletes`: Documents deleted and the versions assigned to their delete operations. +* `deleteByQuery`: Delete-by-query operations and the versions assigned to them. -For example, adding a document with ID `1` can return `"adds":["1",123456789]`. +For example, adding a document with ID `1` can return: + +[source,json] +---- +{ + "adds": [ + { + "id": "1", + "version": 123456789 + } + ] +} +---- + +The `version` value is the version assigned by Solr. Delete versions are commonly negative. The delete fields apply to formats that accept delete commands, such as XML and Javabin. +These v2 response fields use typed objects rather than the alternating name/version arrays returned by v1. == XML Document Indexing diff --git a/solr/webapp/web/js/angular/controllers/documents.js b/solr/webapp/web/js/angular/controllers/documents.js index d38265a05486..36e0d9488cd1 100644 --- a/solr/webapp/web/js/angular/controllers/documents.js +++ b/solr/webapp/web/js/angular/controllers/documents.js @@ -24,7 +24,8 @@ var DOC_PLACEHOLDER = '\n' + var ADD_PLACEHOLDER = '\n' + DOC_PLACEHOLDER + '\n'; solrAdminApp.controller('DocumentsController', - function($scope, $rootScope, $routeParams, $location, Luke, Update, FileUpload, Constants) { + function($scope, $rootScope, $routeParams, $location, Luke, Update, UpdateV2, FileUpload, + Constants, ApiErrorHandler) { $scope.resetMenu("documents", Constants.IS_COLLECTION_PAGE); $scope.refresh = function () { @@ -104,6 +105,40 @@ solrAdminApp.controller('DocumentsController', } } if (!doingFileUpload) { + // Use the typed v2 update endpoints for the standard handler. Custom request + // handlers and the raw Solr command editor retain the v1 path because they may + // use handler-specific parameters or a format selected from the request body. + var useV2 = $scope.handler == "/update" && $scope.isCloudEnabled !== undefined && + ($scope.type == "json" || $scope.type == "wizard" || + $scope.type == "xml" || $scope.type == "csv"); + if (useV2) { + var indexType = $scope.isCloudEnabled ? "collections" : "cores"; + var updateOptions = { + commitWithin: $scope.commitWithin, + overwrite: $scope.overwrite + }; + var v2Callback = function (error, data, response) { + if (error) { + $scope.responseStatus = "failure"; + $scope.response = JSON.stringify((response && response.body) || error, null, ' '); + ApiErrorHandler.handle(response); + return; + } + $scope.responseStatus = "success"; + $scope.response = JSON.stringify(data, null, ' '); + $scope.$evalAsync(); + }; + if (contentType == "json") { + // The generic endpoint preserves ordinary document fields. The explicit + // /update/json endpoint inherits the sample config's split-mode settings. + UpdateV2.update(indexType, $routeParams.core, postData, updateOptions, v2Callback); + } else if (contentType == "xml") { + UpdateV2.updateXml(indexType, $routeParams.core, postData, updateOptions, v2Callback); + } else if (contentType == "csv") { + UpdateV2.updateCsv(indexType, $routeParams.core, postData, updateOptions, v2Callback); + } + return; + } var callback = function (success) { $scope.responseStatus = "success"; delete success.$promise; @@ -134,4 +169,3 @@ solrAdminApp.controller('DocumentsController', } } }); - diff --git a/solr/webapp/web/js/angular/services.js b/solr/webapp/web/js/angular/services.js index 59c9add06354..7571df1c4f74 100644 --- a/solr/webapp/web/js/angular/services.js +++ b/solr/webapp/web/js/angular/services.js @@ -112,6 +112,12 @@ solrAdminServices.factory('Metrics', delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"]; return new solrApi.SystemApi(); }) +.factory('UpdateV2', + function() { + solrApi.ApiClient.instance.basePath = '/api'; + delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"]; + return new solrApi.UpdateApi(); + }) .factory('AliasesV2', function() { solrApi.ApiClient.instance.basePath = '/api'; From ec090e182af2ca4fe2218239e1f2c14c35823d3d Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 14:18:28 -0400 Subject: [PATCH 27/40] Due to limitation in v2 architecture for content types, by swapping to json we simplify our code. Loosing the xml or json and just having JSON seems fine. --- .../webapp/AdminUiDocumentsScreenTest.java | 3 + .../web/js/angular/controllers/documents.js | 156 +++++++----------- solr/webapp/web/partials/documents.html | 12 +- 3 files changed, 64 insertions(+), 107 deletions(-) diff --git a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiDocumentsScreenTest.java b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiDocumentsScreenTest.java index 4ec9186a58db..66c0d6db7b14 100644 --- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiDocumentsScreenTest.java +++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiDocumentsScreenTest.java @@ -44,6 +44,9 @@ public void testDocumentsScreenForm() { assertTrue("Doc type dropdown should offer JSON, got " + types, types.contains("JSON")); assertTrue("Doc type dropdown should offer XML, got " + types, types.contains("XML")); assertTrue("Doc type dropdown should offer CSV, got " + types, types.contains("CSV")); + assertTrue( + "Doc type dropdown should offer raw JSON commands, got " + types, + types.contains("Solr Command (raw JSON)")); waitFor(By.id("submit")); assertNoSevereConsoleErrors(); } diff --git a/solr/webapp/web/js/angular/controllers/documents.js b/solr/webapp/web/js/angular/controllers/documents.js index 36e0d9488cd1..5b1935e06872 100644 --- a/solr/webapp/web/js/angular/controllers/documents.js +++ b/solr/webapp/web/js/angular/controllers/documents.js @@ -17,15 +17,22 @@ //helper for formatting JSON and others var DOC_PLACEHOLDER = '\n' + - 'change.me' + - 'change.me' + + ' change.me\n' + + ' change.me\n' + ''; -var ADD_PLACEHOLDER = '\n' + DOC_PLACEHOLDER + '\n'; +var JSON_DOC_PLACEHOLDER = '{\n' + + ' "id": "change.me",\n' + + ' "title": "change.me"\n' + + '}'; +var JSON_COMMAND_PLACEHOLDER = '{\n' + + ' "add": {\n' + + ' "doc": ' + JSON_DOC_PLACEHOLDER + '\n' + + ' }\n' + + '}'; solrAdminApp.controller('DocumentsController', - function($scope, $rootScope, $routeParams, $location, Luke, Update, UpdateV2, FileUpload, - Constants, ApiErrorHandler) { + function($scope, $routeParams, Luke, UpdateV2, FileUpload, Constants, ApiErrorHandler) { $scope.resetMenu("documents", Constants.IS_COLLECTION_PAGE); $scope.refresh = function () { @@ -35,7 +42,6 @@ solrAdminApp.controller('DocumentsController', $scope.fields = Object.keys(data.schema.fields); }); $scope.document = ""; - $scope.handler = "/update"; $scope.type = "json"; $scope.commitWithin = 1000; $scope.overwrite = true; @@ -46,11 +52,11 @@ solrAdminApp.controller('DocumentsController', $scope.changeDocumentType = function () { $scope.placeholder = ""; if ($scope.type == 'json') { - $scope.placeholder = '{"id":"change.me","title":"change.me"}'; + $scope.placeholder = JSON_DOC_PLACEHOLDER; } else if ($scope.type == 'csv') { $scope.placeholder = "id,title\nchange.me,change.me"; - } else if ($scope.type == 'solr') { - $scope.placeholder = ADD_PLACEHOLDER; + } else if ($scope.type == 'solr-json') { + $scope.placeholder = JSON_COMMAND_PLACEHOLDER; } else if ($scope.type == 'xml') { $scope.placeholder = DOC_PLACEHOLDER; } @@ -65,107 +71,57 @@ solrAdminApp.controller('DocumentsController', }; $scope.submit = function () { - var contentType = ""; - var postData = ""; - var params = {}; - var doingFileUpload = false; - - if ($scope.handler[0] == '/') { - params.handler = $scope.handler.substring(1); - } else { - params.handler = 'update'; - params.qt = $scope.handler; + if ($scope.type == "upload") { + FileUpload.upload({ + core: $routeParams.core, + handler: "update", + commitWithin: $scope.commitWithin, + overwrite: $scope.overwrite, + wt: "json", + raw: $scope.literalParams + }, $scope.fileUpload, function (data) { + $scope.responseStatus = "success"; + $scope.response = JSON.stringify(data, null, ' '); + }, function (data) { + $scope.responseStatus = "failure"; + $scope.response = JSON.stringify(data, null, ' '); + }); + return; } - params.commitWithin = $scope.commitWithin; - params.overwrite = $scope.overwrite; - params.core = $routeParams.core; - params.wt = "json"; - + var postData; + var updateMethod; if ($scope.type == "json" || $scope.type == "wizard") { postData = "[" + $scope.document + "]"; - contentType = "json"; - } else if ($scope.type == "csv") { + updateMethod = UpdateV2.update; + } else if ($scope.type == "solr-json") { postData = $scope.document; - contentType = "csv"; + updateMethod = UpdateV2.update; } else if ($scope.type == "xml") { postData = "" + $scope.document + ""; - contentType = "xml"; - } else if ($scope.type == "upload") { - doingFileUpload = true; - params.raw = $scope.literalParams; - } else if ($scope.type == "solr") { + updateMethod = UpdateV2.updateXml; + } else if ($scope.type == "csv") { postData = $scope.document; - if (postData[0] == "<") { - contentType = "xml"; - } else if (postData[0] == "{" || postData[0] == '[') { - contentType = "json"; - } else { - alert("Cannot identify content type") - } + updateMethod = UpdateV2.updateCsv; } - if (!doingFileUpload) { - // Use the typed v2 update endpoints for the standard handler. Custom request - // handlers and the raw Solr command editor retain the v1 path because they may - // use handler-specific parameters or a format selected from the request body. - var useV2 = $scope.handler == "/update" && $scope.isCloudEnabled !== undefined && - ($scope.type == "json" || $scope.type == "wizard" || - $scope.type == "xml" || $scope.type == "csv"); - if (useV2) { - var indexType = $scope.isCloudEnabled ? "collections" : "cores"; - var updateOptions = { - commitWithin: $scope.commitWithin, - overwrite: $scope.overwrite - }; - var v2Callback = function (error, data, response) { - if (error) { - $scope.responseStatus = "failure"; - $scope.response = JSON.stringify((response && response.body) || error, null, ' '); - ApiErrorHandler.handle(response); - return; - } - $scope.responseStatus = "success"; - $scope.response = JSON.stringify(data, null, ' '); - $scope.$evalAsync(); - }; - if (contentType == "json") { - // The generic endpoint preserves ordinary document fields. The explicit - // /update/json endpoint inherits the sample config's split-mode settings. - UpdateV2.update(indexType, $routeParams.core, postData, updateOptions, v2Callback); - } else if (contentType == "xml") { - UpdateV2.updateXml(indexType, $routeParams.core, postData, updateOptions, v2Callback); - } else if (contentType == "csv") { - UpdateV2.updateCsv(indexType, $routeParams.core, postData, updateOptions, v2Callback); - } + if (!updateMethod || $scope.isCloudEnabled === undefined) return; + + var indexType = $scope.isCloudEnabled ? "collections" : "cores"; + var updateOptions = { + commitWithin: $scope.commitWithin, + overwrite: $scope.overwrite + }; + var v2Callback = function (error, data, response) { + if (error) { + $scope.responseStatus = "failure"; + $scope.response = JSON.stringify((response && response.body) || error, null, ' '); + ApiErrorHandler.handle(response); return; } - var callback = function (success) { - $scope.responseStatus = "success"; - delete success.$promise; - delete success.$resolved; - $scope.response = JSON.stringify(success, null, ' '); - }; - var failure = function (failure) { - $scope.responseStatus = failure; - }; - if (contentType == "json") { - Update.postJson(params, postData, callback, failure); - } else if (contentType == "xml") { - Update.postXml(params, postData, callback, failure); - } else if (contentType == "csv") { - Update.postCsv(params, postData, callback, failure); - } - } else { - var file = $scope.fileUpload; - console.log('file is ' + JSON.stringify(file)); - var uploadUrl = "/fileUpload"; - FileUpload.upload(params, $scope.fileUpload, function (success) { - $scope.responseStatus = "success"; - $scope.response = JSON.stringify(success, null, ' '); - }, function (failure) { - $scope.responseStatus = "failure"; - $scope.response = JSON.stringify(failure, null, ' '); - }); - } + $scope.responseStatus = "success"; + $scope.response = JSON.stringify(data, null, ' '); + $scope.$evalAsync(); + }; + updateMethod.call(UpdateV2, indexType, $routeParams.core, postData, updateOptions, v2Callback); } }); diff --git a/solr/webapp/web/partials/documents.html b/solr/webapp/web/partials/documents.html index 2bf3f12982dc..29e26aaa7c53 100644 --- a/solr/webapp/web/partials/documents.html +++ b/solr/webapp/web/partials/documents.html @@ -20,10 +20,10 @@
- - +
+ Documents are submitted to the v2 endpoint + /api/{{isCloudEnabled ? 'collections' : 'cores'}}/{{currentCollection.name || currentCore.name}}/update. +
@@ -34,7 +34,7 @@ - +
@@ -107,5 +107,3 @@
- - From 752344179a6a2df0ca48cb63bba54d67d45b2567 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 14:22:42 -0400 Subject: [PATCH 28/40] fix the layout issue that has plagued this page where results showed up below the fold on the right ,not at top --- solr/webapp/web/css/angular/documents.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/solr/webapp/web/css/angular/documents.css b/solr/webapp/web/css/angular/documents.css index 2f0ba12ed7fd..4283b96741e9 100644 --- a/solr/webapp/web/css/angular/documents.css +++ b/solr/webapp/web/css/angular/documents.css @@ -27,7 +27,7 @@ limitations under the License. #content #documents #form { float: left; - /*width: 21%;*/ + width: 43%; } #content #documents #form label @@ -49,7 +49,8 @@ limitations under the License. #content #documents #form textarea { margin-bottom: 2px; - /*width: 98%;*/ + box-sizing: border-box; + max-width: 100%; } #content #documents #form #start @@ -123,6 +124,7 @@ limitations under the License. { float: right; width: 54%; + box-sizing: border-box; } #content #documents #result #url From 8723661daf1281416499b06cccc5fcbdc000fc31 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 14:30:15 -0400 Subject: [PATCH 29/40] pretty print the sample raw json command by just formatting it here. --- solr/webapp/web/js/angular/controllers/documents.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/solr/webapp/web/js/angular/controllers/documents.js b/solr/webapp/web/js/angular/controllers/documents.js index 5b1935e06872..78505fd3d9b6 100644 --- a/solr/webapp/web/js/angular/controllers/documents.js +++ b/solr/webapp/web/js/angular/controllers/documents.js @@ -27,7 +27,10 @@ var JSON_DOC_PLACEHOLDER = '{\n' + '}'; var JSON_COMMAND_PLACEHOLDER = '{\n' + ' "add": {\n' + - ' "doc": ' + JSON_DOC_PLACEHOLDER + '\n' + + ' "doc" {\n' + + ' "id": "change.me",\n' + + ' "title": "change.me"\n' + + ' }\n' + ' }\n' + '}'; From 9b00cce8d06c589a6234ac38b7d50108ebce6b91 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 14:30:31 -0400 Subject: [PATCH 30/40] No longer need v1 update --- .../webapp/web/js/angular/controllers/core-overview.js | 2 +- solr/webapp/web/js/angular/controllers/cores.js | 2 +- solr/webapp/web/js/angular/services.js | 10 ---------- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/solr/webapp/web/js/angular/controllers/core-overview.js b/solr/webapp/web/js/angular/controllers/core-overview.js index 4c97e6d12b07..7cc72a608fef 100644 --- a/solr/webapp/web/js/angular/controllers/core-overview.js +++ b/solr/webapp/web/js/angular/controllers/core-overview.js @@ -16,7 +16,7 @@ */ solrAdminApp.controller('CoreOverviewController', -function($scope, $rootScope, $routeParams, Luke, CoreInfo, Update, Replication, Ping, Constants) { +function($scope, $rootScope, $routeParams, Luke, CoreInfo, Replication, Ping, Constants) { $scope.resetMenu("overview", Constants.IS_CORE_PAGE); $scope.refreshIndex = function() { Luke.index({core: $routeParams.core}, diff --git a/solr/webapp/web/js/angular/controllers/cores.js b/solr/webapp/web/js/angular/controllers/cores.js index c6c6b311488f..dde76d94563e 100644 --- a/solr/webapp/web/js/angular/controllers/cores.js +++ b/solr/webapp/web/js/angular/controllers/cores.js @@ -16,7 +16,7 @@ */ solrAdminApp.controller('CoreAdminController', - function($scope, $routeParams, $location, $timeout, $route, CoresV2, Update, Constants, ApiErrorHandler){ + function($scope, $routeParams, $location, $timeout, $route, CoresV2, Constants, ApiErrorHandler){ $scope.resetMenu("cores", Constants.IS_ROOT_PAGE); $scope.selectedCore = $routeParams.corename; // use 'corename' not 'core' to distinguish from /solr/:core/ $scope.refresh = function() { diff --git a/solr/webapp/web/js/angular/services.js b/solr/webapp/web/js/angular/services.js index 7571df1c4f74..ed6bbff41a95 100644 --- a/solr/webapp/web/js/angular/services.js +++ b/solr/webapp/web/js/angular/services.js @@ -233,16 +233,6 @@ solrAdminServices.factory('Metrics', ['$resource', function($resource) { return $resource(':core/admin/info', {wt:'json', core: "@core", _:Date.now()}); }]) -.factory('Update', - ['$resource', function($resource) { - return $resource(':core/:handler', {core: '@core', wt:'json', _:Date.now(), handler:'update'}, { - "commit": {params: {commit: "true"}}, - "post": {headers: {'Content-type': 'application/json'}, method: "POST", params: {handler: '@handler'}}, - "postJson": {headers: {'Content-type': 'application/json'}, method: "POST", params: {handler: '@handler'}}, - "postXml": {headers: {'Content-type': 'text/xml'}, method: "POST", params: {handler: '@handler'}}, - "postCsv": {headers: {'Content-type': 'application/csv'}, method: "POST", params: {handler: '@handler'}} - }); - }]) .factory('ParamSet', ['$resource', function($resource) { // v2 GetConfigAPI/ModifyParamSetAPI (/api/(cores|collections)/:core/config/params) still From 8971144d4720bc3e15a1c8b762ba350dd7bd7faf Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 14:53:02 -0400 Subject: [PATCH 31/40] string formatting is hard. --- solr/webapp/web/js/angular/controllers/documents.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solr/webapp/web/js/angular/controllers/documents.js b/solr/webapp/web/js/angular/controllers/documents.js index 78505fd3d9b6..3629d12a9fa2 100644 --- a/solr/webapp/web/js/angular/controllers/documents.js +++ b/solr/webapp/web/js/angular/controllers/documents.js @@ -27,7 +27,7 @@ var JSON_DOC_PLACEHOLDER = '{\n' + '}'; var JSON_COMMAND_PLACEHOLDER = '{\n' + ' "add": {\n' + - ' "doc" {\n' + + ' "doc": {\n' + ' "id": "change.me",\n' + ' "title": "change.me"\n' + ' }\n' + From 37d6f2253416696d79d1bdfff185f97a650705a1 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 14:53:11 -0400 Subject: [PATCH 32/40] better changelog --- .../unreleased/SOLR-18457-migrate-update-api-to-jax-rs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/unreleased/SOLR-18457-migrate-update-api-to-jax-rs.yml b/changelog/unreleased/SOLR-18457-migrate-update-api-to-jax-rs.yml index 9a2335479977..3d2849b259c2 100644 --- a/changelog/unreleased/SOLR-18457-migrate-update-api-to-jax-rs.yml +++ b/changelog/unreleased/SOLR-18457-migrate-update-api-to-jax-rs.yml @@ -1,4 +1,4 @@ -title: Migrate v2 update endpoints to JAX-RS and rename the Javabin endpoint to /update/javabin +title: Migrate v2 update endpoints to JAX-RS and rename the Javabin endpoint to /update/javabin. Migrate Admin UI to using v2 endpoints, fixing UI issues in the document upload screen. Document screen only works with /upload endpoint, no longer can specify a custom endpoint. type: changed authors: - name: Eric Pugh From 0f23c24099d03919ce58a3075babd0eef2777c73 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 16:59:23 -0400 Subject: [PATCH 33/40] Collapse seperate v2 into v1, and add javabin and version response info --- .../modules/indexing-guide/indexing-nav.adoc | 1 - .../pages/documents-screen.adoc | 11 +- .../pages/indexing-with-update-handlers.adoc | 458 +++++++++++++++--- .../pages/indexing-with-v2-apis.adoc | 211 -------- 4 files changed, 409 insertions(+), 272 deletions(-) delete mode 100644 solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc diff --git a/solr/solr-ref-guide/modules/indexing-guide/indexing-nav.adoc b/solr/solr-ref-guide/modules/indexing-guide/indexing-nav.adoc index 22a98a6abd1f..940225e8d4ef 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/indexing-nav.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/indexing-nav.adoc @@ -50,7 +50,6 @@ * Indexing & Data Operations ** xref:indexing-with-update-handlers.adoc[] *** xref:transforming-and-indexing-custom-json.adoc[] -*** xref:indexing-with-v2-apis.adoc[] ** xref:indexing-with-cbor.adoc[] ** xref:indexing-with-tika.adoc[] ** xref:indexing-nested-documents.adoc[] diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/documents-screen.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/documents-screen.adoc index 13c96ecc5b95..470cb7995ff7 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/pages/documents-screen.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/pages/documents-screen.adoc @@ -36,9 +36,8 @@ There are other ways to load data, see also these sections: ==== == Common Fields -* Request-Handler: The first step is to define the RequestHandler. -By default `/update` will be defined. -Change the request handler to `/update/extract` to use Solr Cell, if you are running a Tika Server. +* Endpoint: The Documents screen submits updates to the v2 `/api/cores/\{core\}/update` or +`/api/collections/\{collection\}/update` endpoint, depending on whether Solr is running in standalone or SolrCloud mode. * Document Type: Select the Document Type to define the format of the document to load. The remaining parameters may change depending on the document type selected. * Document(s): Enter a properly formatted Solr document corresponding to the `Document Type` selected. @@ -69,16 +68,16 @@ The Document Builder provides a wizard-like interface to enter fields of a docum == File Upload The File Upload option allows choosing a prepared file and uploading it. -If using `/update` for the Request-Handler option, you will be limited to XML, CSV, and JSON. +The File Upload option currently uses the v1 `/update` handler and supports XML, CSV, and JSON files. Other document types (e.g., Word, PDF, etc.) can be indexed using the ExtractingRequestHandler (aka, Solr Cell). -You must modify the RequestHandler to `/update/extract`, which must be defined in your `solrconfig.xml` file with your desired defaults. +Solr Cell uploads through `/update/extract` must be sent directly to that configured request handler. You should also add `&literal.id` shown in the "Extracting Request Handler Params" field so the file chosen is given a unique id. More information can be found in xref:indexing-with-tika.adoc[]. == Solr Command -The Solr Command option allows you to use the `/update` request handler with XML or JSON formatted commands to perform specific actions. +The Solr Command option allows you to use the v2 `/update` endpoint with JSON formatted commands to perform specific actions. A few examples are: * Deleting documents diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc index 456779717350..1c875356a8df 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc @@ -1,5 +1,5 @@ = Indexing with Update Handlers -:page-children: transforming-and-indexing-custom-json, indexing-with-v2-apis +:page-children: transforming-and-indexing-custom-json // 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 @@ -21,7 +21,7 @@ Update handlers are request handlers designed to add, delete and update document In addition to having plugins for importing rich documents (see xref:indexing-with-tika.adoc[]), Solr natively supports indexing structured documents in XML, CSV, and JSON. NOTE: Solr also exposes update functionality via the xref:configuration-guide:v2-api.adoc[v2 API]. -See xref:indexing-with-v2-apis.adoc[] for details on the v2 update endpoints and their differences from the v1 handlers described on this page. +The v2 update endpoints are documented in the <> section below. The recommended way to configure and use request handlers is with path based names that map to paths in the request URL. However, request handlers can also be specified with the `qt` (query type) parameter if the xref:configuration-guide:requestdispatcher.adoc[`requestDispatcher`] is appropriately configured. @@ -41,6 +41,89 @@ The default configuration file has the update request handler configured by defa ---- +== Versioned Update Responses + +Add the `versions=true` query parameter to an update request when the response must include the versions assigned to add or delete operations. +This parameter is supported by both the V1 update handlers and the V2 update endpoints. +It can be used with JSON, XML, CSV, Javabin, and CBOR update requests where the format is supported by the endpoint. +The `adds` field applies to document-ingest formats; `deletes` and `deleteByQuery` apply to formats that accept delete commands, such as JSON command updates, XML, and Javabin. + +See xref:partial-document-updates.adoc#optimistic-concurrency[Optimistic Concurrency] for more information about update versions. + +[tabs#versioned-update-response] +====== +V1 API:: ++ +==== +V1 returns alternating name/version values. +[source,json] +---- +{ + "responseHeader": { + "status": 0, + "QTime": 127 + }, + "adds": [ + "0002166313", + 123456789 + ], + "deletes": [ + "0002166314", + -123456790 + ], + "deleteByQuery": [ + "status:deleted", + -123456791 + ] +} +---- +==== + +V2 API:: ++ +==== +The v2 API returns typed entries: + +[source,json] +---- +{ + "responseHeader": { + "status": 0, + "QTime": 127 + }, + "adds": [ + { + "id": "0002166313", + "version": 123456789 + } + ], + "deletes": [ + { + "id": "0002166314", + "version": -123456790 + } + ], + "deleteByQuery": [ + { + "query": "status:deleted", + "version": -123456791 + } + ] +} +---- +==== +====== + +The status field will be non-zero in case of failure. + +The response fields represent: + +* `adds`: Documents added and the versions assigned to them. +* `deletes`: Documents deleted and the versions assigned to their delete operations. +* `deleteByQuery`: Delete-by-query operations and the versions assigned to them. + +The version value is assigned by Solr. Delete versions are commonly negative. + == XML Formatted Index Updates Index update commands can be sent as XML message to the update handler using `Content-type: application/xml` or `Content-type: text/xml`. @@ -156,12 +239,32 @@ Default is unlimited, resulting segments respect the `maxMergedSegmentMB` settin Here are examples of `` and `` using optional attributes: -[source,xml] +[tabs#xml-commit-request] +====== +V1 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d ' + + +' ---- +==== + +V2 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d ' - +' ---- +==== +====== ==== Delete Operations @@ -171,15 +274,38 @@ It doesn't work for child/nested docs. "Delete by Query" deletes all documents matching a specified query, although `commitWithin` is ignored for a Delete by Query. A single delete message can contain multiple delete operations. -[source,xml] +[tabs#xml-delete-request] +====== +V1 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d ' + + 0002166313 + 0031745983 + subject:sport + publisher:penguin +' +---- +==== + +V2 API:: ++ +==== +[source,bash] ---- +curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d ' 0002166313 0031745983 subject:sport publisher:penguin - +' ---- +==== +====== [IMPORTANT] ==== @@ -197,12 +323,43 @@ The rollback command rolls back all add and deletes made to the index since the It neither calls any event listeners nor creates a new searcher. Its syntax is simple: ``. +The same command can be sent through either API: + +[tabs#xml-rollback-request] +====== +V1 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/solr/my_collection/update \ + -H "Content-Type: text/xml" -d '' +---- +==== + +V2 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/api/collections/my_collection/update/xml \ + -H "Content-Type: text/xml" -d '' +---- +==== +====== + ==== Grouping Operations You can post several commands in a single XML file by grouping them with the surrounding `` element. -[source,xml] +[tabs#xml-grouped-update-request] +====== +V1 API:: ++ +==== +[source,bash] ---- +curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d ' @@ -213,18 +370,44 @@ You can post several commands in a single XML file by grouping them with the sur 0002166313 - +' ---- +==== +V2 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d ' + + + + + + + + + 0002166313 + +' +---- +==== +====== === Using curl to Perform Updates -You can use the `curl` utility to perform any of the above commands, using its `--data-binary` option to append the XML message to the `curl` command, and generating a HTTP POST request. -For example: +You can use the `curl` utility to perform any of the above commands, using its `-d` option to append the XML message to the `curl` command, and generating a HTTP POST request. +The same XML update can be sent through either API: +[tabs#xml-update-request] +====== +V1 API:: ++ +==== [source,bash] ---- -curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" --data-binary ' +curl http://localhost:8983/solr/my_collection/update?versions=true -H "Content-Type: text/xml" -d ' Patrick Eagar @@ -236,39 +419,80 @@ curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" ' ---- +==== + +V2 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/api/collections/my_collection/update/xml?versions=true -H "Content-Type: text/xml" -d ' + + + Patrick Eagar + Sports + 796.35 + 0002166313 + 1982 + Collins + +' +---- +==== +====== For posting XML messages contained in a file, you can use the alternative form: +[tabs#xml-file-update-request] +====== +V1 API:: ++ +==== [source,bash] ---- -curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" --data-binary @myfile.xml +curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d @myfile.xml ---- +==== -The approach above works well, but using the `--data-binary` option causes `curl` to load the whole `myfile.xml` into memory before posting it to server. +V2 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d @myfile.xml +---- +==== +====== + +The approach above works well, but using the `-d` option causes `curl` to load the whole `myfile.xml` into memory before posting it to server. This may be problematic when dealing with multi-gigabyte files. This alternative `curl` command performs equivalent operations but with minimal `curl` memory usage: +[tabs#xml-file-streaming-request] +====== +V1 API:: ++ +==== [source,bash] ---- curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -T "myfile.xml" -X POST ---- +==== -Responses from Solr take the form shown here: - -[source,xml] +V2 API:: ++ +==== +[source,bash] ---- - - - 0 - 127 - - +curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -T "myfile.xml" -X POST ---- - -The status field will be non-zero in case of failure. +==== +====== === Using XSLT to Transform XML Index Updates +NOTE: The XSLT update handler is V1-only. V2 does not provide a configurable equivalent for custom request handlers such as `/update/xslt`; use the V1 endpoint for these requests. + The xref:configuration-guide:script-update-processor.adoc[Scripting module] provides a separate XSLT Update Request Handler that allows you to index any arbitrary XML by using the `` parameter to apply an https://en.wikipedia.org/wiki/XSLT[XSL transformation]. You must have an XSLT stylesheet in the `conf/xslt` directory of your xref:configuration-guide:config-sets.adoc[configset] that can transform the incoming data to the expected `` format, and use the `tr` parameter to specify the name of that stylesheet. @@ -347,9 +571,9 @@ One example usage would be to copy a Solr 1.3 index (which does not have CSV res [source,bash] ---- -$ curl -o standard_solr_xml_format.xml "http://localhost:8983/solr/techproducts/select?q=ipod&fl=id,cat,name,popularity,price,score&wt=xml" +$ curl "http://localhost:8983/solr/techproducts/select?q=ipod&fl=id,cat,name,popularity,price,score&wt=xml" -o standard_solr_xml_format.xml -$ curl -X POST -H "Content-Type: text/xml" -d @standard_solr_xml_format.xml "http://localhost:8983/solr/techproducts/update/xslt?commit=true&tr=updateXml.xsl" +$ curl "http://localhost:8983/solr/techproducts/update/xslt?commit=true&tr=updateXml.xsl" -H "Content-Type: text/xml" -d @standard_solr_xml_format.xml ---- NOTE: You can see the opposite export/import cycle using the `tr` parameter in the xref:query-guide:response-writers.adoc#xslt-writer-example[Response Writer XSLT example]. @@ -372,24 +596,53 @@ To differentiate this from a set of commands, the `json.command=false` request p ==== Adding a Single JSON Document -The simplest way to add documents via JSON is to send each document individually as a JSON Object, using the `/update/json/docs` path: +The simplest way to add documents via JSON is to send each document individually as a JSON Object. + +[tabs#json-single-document-request] +====== +V1 API:: ++ +==== +The V1 document-only endpoint is `/update/json/docs`: + +[source,bash] +---- +curl 'http://localhost:8983/solr/my_collection/update/json/docs' -H 'Content-Type: application/json' -d ' +{ + "id": "1", + "title": "Doc 1" +}' +---- +==== + +V2 API:: ++ +==== +The V2 document-only endpoint is `/update/json`: [source,bash] ---- -curl -X POST -H 'Content-Type: application/json' 'http://localhost:8983/solr/my_collection/update/json/docs' --data-binary ' +curl 'http://localhost:8983/api/collections/my_collection/update/json' -H 'Content-Type: application/json' -d ' { "id": "1", "title": "Doc 1" }' ---- +==== +====== ==== Adding Multiple JSON Documents Adding multiple documents at one time via JSON can be done via a JSON Array of JSON Objects, where each object represents a document: +[tabs#json-multiple-document-request] +====== +V1 API:: ++ +==== [source,bash] ---- -curl -X POST -H 'Content-Type: application/json' 'http://localhost:8983/solr/my_collection/update' --data-binary ' +curl 'http://localhost:8983/solr/my_collection/update' -H 'Content-Type: application/json' -d ' [ { "id": "1", @@ -401,22 +654,64 @@ curl -X POST -H 'Content-Type: application/json' 'http://localhost:8983/solr/my_ } ]' ---- +==== -A sample JSON file is provided at `example/exampledocs/books.json` and contains an array of objects that you can add to the Solr "techproducts" example: +V2 API:: ++ +==== +[source,bash] +---- +curl 'http://localhost:8983/api/collections/my_collection/update' -H 'Content-Type: application/json' -d ' +[ + { + "id": "1", + "title": "Doc 1" + }, + { + "id": "2", + "title": "Doc 2" + } +]' +---- +==== +====== +A JSON file can be submitted in the same way: + +[tabs#json-file-request] +====== +V1 API:: ++ +==== [source,bash] ---- -curl 'http://localhost:8983/solr/techproducts/update?commit=true' --data-binary @example/exampledocs/books.json -H 'Content-type:application/json' +curl 'http://localhost:8983/solr/techproducts/update?commit=true' -H 'Content-type:application/json' -d @example/exampledocs/books.json ---- +==== + +V2 API:: ++ +==== +[source,bash] +---- +curl 'http://localhost:8983/api/collections/techproducts/update?commit=true' -H 'Content-type:application/json' -d @example/exampledocs/books.json +---- +==== +====== ==== Sending JSON Update Commands In general, the JSON update syntax supports all of the update commands that the XML update handler supports, through a straightforward mapping. Multiple commands, adding and deleting documents, may be contained in one message: +[tabs#json-command-request] +====== +V1 API:: ++ +==== [source,bash,subs="verbatim,callouts"] ---- -curl -X POST -H 'Content-Type: application/json' 'http://localhost:8983/solr/my_collection/update' --data-binary ' +curl 'http://localhost:8983/solr/my_collection/update' -H 'Content-Type: application/json' -d ' { "add": { "doc": { @@ -441,7 +736,40 @@ curl -X POST -H 'Content-Type: application/json' 'http://localhost:8983/solr/my_ "delete": { "query":"QUERY" } --<6> }' ---- +==== +V2 API:: ++ +==== +[source,bash,subs="verbatim,callouts"] +---- +curl 'http://localhost:8983/api/collections/my_collection/update' -H 'Content-Type: application/json' -d ' +{ + "add": { + "doc": { + "id": "DOC1", + "my_field": 2.3, + "my_multivalued_field": [ "aaa", "bbb" ] --<1> + } + }, + "add": { + "commitWithin": 5000, --<2> + "overwrite": false, --<3> + "doc": { + "f1": "v1", --<4> + "f1": "v2" + } + }, + + "commit": {}, + "optimize": { "waitSearcher":false }, + + "delete": { "id":"ID" }, --<5> + "delete": { "query":"QUERY" } --<6> +}' +---- +==== +====== <1> Can use an array for a multi-valued field <2> Commit this document within 5 seconds <3> Don't check for existing documents with the same uniqueKey @@ -483,20 +811,11 @@ You can specify the version of deletes in the body of the update request as well === JSON Update Convenience Paths -In addition to the `/update` handler, there are a few additional JSON specific request handler paths available by default in Solr, that implicitly override the behavior of some request parameters: - -[width="100%",options="header",] -|=== -|Path |Default Parameters -|`/update/json` |`update.contentType=application/json` -|`/update/json/docs` a| -`stream.contentType=application/json` - -`json.command=false` - -|=== +The V1 `/update/json` endpoint is a specialized Solr request path designed for clients that cannot easily set the required `Content-Type: application/json` header, as this route automatically forces Solr to treat the incoming payload as JSON. +The V1 `/update/json/docs` path additionally forces document-only processing through `stream.contentType=application/json` and `json.command=false`. -The `/update/json` path may be useful for clients sending in JSON formatted update commands from applications where setting the Content-Type proves difficult, while the `/update/json/docs` path can be particularly convenient for clients that always want to send in documents – either individually or as a list – without needing to worry about the full JSON command syntax. +In V2, use the general `/api/collections/\{collection\}/update` or `/api/cores/\{core\}/update` endpoint with `Content-Type: application/json` for JSON update commands. +Use `/api/collections/\{collection\}/update/json` or `/api/cores/\{core\}/update/json` for document-only JSON requests. === Custom JSON Documents @@ -510,10 +829,26 @@ CSV formatted update requests may be sent to Solr's `/update` handler using `Con A sample CSV file is provided at `example/exampledocs/books.csv` that you can use to add some documents to the Solr "techproducts" example: +[tabs#csv-file-request] +====== +V1 API:: ++ +==== +[source,bash] +---- +curl 'http://localhost:8983/solr/my_collection/update?commit=true' -H 'Content-type:application/csv' -d @example/exampledocs/books.csv +---- +==== + +V2 API:: ++ +==== [source,bash] ---- -curl 'http://localhost:8983/solr/my_collection/update?commit=true' --data-binary @example/exampledocs/books.csv -H 'Content-type:application/csv' +curl 'http://localhost:8983/api/collections/my_collection/update?commit=true' -H 'Content-type:application/csv' -d @example/exampledocs/books.csv ---- +==== +====== === CSV Update Parameters @@ -736,19 +1071,34 @@ SELECT * INTO OUTFILE '/tmp/result.txt' FROM mytable; This file could then be imported into Solr by setting the `separator` to tab (%09) and the `escape` to backslash (%5c). +[tabs#csv-tab-delimited-request] +====== +V1 API:: ++ +==== [source,bash] ---- -curl 'http://localhost:8983/solr/my_collection/update/csv?commit=true&separator=%09&escape=%5c' --data-binary @/tmp/result.txt +curl 'http://localhost:8983/solr/my_collection/update/csv?commit=true&separator=%09&escape=%5c' -d @/tmp/result.txt ---- +==== + +V2 API:: ++ +==== +[source,bash] +---- +curl 'http://localhost:8983/api/collections/my_collection/update/csv?commit=true&separator=%09&escape=%5c' -d @/tmp/result.txt +---- +==== +====== === CSV Update Convenience Paths -In addition to the `/update` handler, there is an additional CSV specific request handler path available by default in Solr, that implicitly override the behavior of some request parameters: +The `/update/csv` endpoint is a specialized Solr request path designed for importing CSV data. +You use it primarily when your client application or script cannot easily set the required `Content-Type: text/csv` header, as this route automatically forces Solr to treat all incoming payloads as CSV data without needing extra parameter configuration. -[cols=",",options="header",] -|=== -|Path |Default Parameters -|`/update/csv` |`update.contentType=application/csv` -|=== +== Javabin Document Indexing -The `/update/csv` path may be useful for clients sending in CSV formatted update commands from applications where setting the Content-Type proves difficult. +Javabin is Solr's native binary update format and is primarily intended for SolrJ clients. +V1 clients send Javabin to `/update` with `Content-Type: application/javabin`. +V2 clients can use `/update/javabin` or the general `/update` endpoint with the same content type. diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc deleted file mode 100644 index ede7485cf1f6..000000000000 --- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc +++ /dev/null @@ -1,211 +0,0 @@ -= Indexing with the V2 Update API -// 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. - -Solr's xref:configuration-guide:v2-api.adoc[v2 API] provides update endpoints under the `/api` path prefix. -These endpoints accept the same document formats as the xref:indexing-with-update-handlers.adoc[v1 update handlers], with some important differences described below. - -NOTE: The v2 API is classified as "experimental" and may change in backwards-incompatible ways. - -== V2 Update Endpoint Paths - -For a SolrCloud collection the v2 update base path is: - ----- -/api/collections/{collection}/update ----- - -For a standalone core the v2 update base path is: - ----- -/api/cores/{core}/update ----- - -The following sub-paths are available: - -[width="100%",options="header",] -|=== -|Path |Accepted Format |Notes -|`/update` |JSON, XML, CSV, Javabin, or CBOR |Selects the update loader from the request `Content-Type`, like v1 `/update` -|`/update/json` |JSON document or array of documents |Equivalent to v1 `/update/json/docs`; documents only (no JSON command syntax) -|`/update/xml` |XML |Supports full XML update syntax (add, delete, commit, optimize) -|`/update/csv` |CSV |Equivalent to v1 `/update/csv` -|`/update/javabin` |Javabin |Equivalent to v1 `/update` with `Content-Type: application/javabin` -|=== - -The v2 `/update` endpoint is the general-purpose update endpoint and selects a loader based on the request `Content-Type`. -When it receives JSON, it supports the full xref:indexing-with-update-handlers.adoc#sending-json-update-commands[JSON update command syntax], like v1 `/update`. - -IMPORTANT: The v2 `/update/json` endpoint is document-only: it processes one or more JSON documents (either a single JSON object or an array of objects, like the v1 `/update/json/docs` path) and does *not* support JSON update commands such as commit, delete, or optimize in the request body. - -== JSON Document Indexing - -The v2 `/update/json` endpoint accepts JSON document(s). The example below shows an array of documents: - -[source,bash] ----- -curl -X POST -H 'Content-Type: application/json' \ - 'http://localhost:8983/api/collections/my_collection/update/json?commit=true' \ - --data-binary ' -[ - { - "id": "1", - "title": "Doc 1" - }, - { - "id": "2", - "title": "Doc 2" - } -]' ----- - -A single document can also be posted as a JSON object: - -[source,bash] ----- -curl -X POST -H 'Content-Type: application/json' \ - 'http://localhost:8983/api/collections/my_collection/update/json?commit=true' \ - --data-binary ' -{ - "id": "1", - "title": "Doc 1" -}' ----- - -Query parameters such as `commit`, `commitWithin`, and `overwrite` can be appended to the URL, as shown above. -Add `versions=true` to an update request to include the assigned document versions in the response. -See xref:partial-document-updates.adoc#optimistic-concurrency[Optimistic Concurrency] for more details. - -When `versions=true` is set, the response can include these fields for the operations in the request: - -* `adds`: Documents added and the versions assigned to them. -* `deletes`: Documents deleted and the versions assigned to their delete operations. -* `deleteByQuery`: Delete-by-query operations and the versions assigned to them. - -For example, adding a document with ID `1` can return: - -[source,json] ----- -{ - "adds": [ - { - "id": "1", - "version": 123456789 - } - ] -} ----- - -The `version` value is the version assigned by Solr. Delete versions are commonly negative. -The delete fields apply to formats that accept delete commands, such as XML and Javabin. -These v2 response fields use typed objects rather than the alternating name/version arrays returned by v1. - -== XML Document Indexing - -The v2 `/update/xml` endpoint accepts the same XML format as the v1 `/update` handler and supports the full XML update syntax, including add, delete, commit, and optimize commands. - -[source,bash] ----- -curl -X POST -H 'Content-Type: application/xml' \ - 'http://localhost:8983/api/collections/my_collection/update/xml' \ - --data-binary ' - - - 1 - Doc 1 - -' ----- - -Delete by ID and delete by query are also supported: - -[source,bash] ----- -curl -X POST -H 'Content-Type: application/xml' \ - 'http://localhost:8983/api/collections/my_collection/update/xml?commit=true' \ - --data-binary ' - - 1 - title:unwanted -' ----- - -== CSV Document Indexing - -The v2 `/update/csv` endpoint accepts CSV-formatted documents and supports the same xref:indexing-with-update-handlers.adoc#csv-update-parameters[CSV update parameters] as the v1 handler: - -[source,bash] ----- -curl -X POST -H 'Content-Type: text/csv' \ - 'http://localhost:8983/api/collections/my_collection/update/csv?commit=true' \ - --data-binary ' -id,title -1,Doc 1 -2,Doc 2' ----- - -== Javabin Document Indexing - -The v2 `/update/javabin` endpoint accepts documents in Javabin format, which is the native binary format used by SolrJ. -This endpoint is primarily intended for use by SolrJ clients. - -== Commit and Rollback - -Because the v2 `/update/json` endpoint does not support update commands in the request body, commits and rollbacks must be issued separately. -Use the `commit=true` or `commitWithin=N` URL parameters to commit after indexing: - -[source,bash] ----- -curl -X POST -H 'Content-Type: application/json' \ - 'http://localhost:8983/api/collections/my_collection/update/json?commit=true' \ - --data-binary '[{"id":"1","title":"Doc 1"}]' ----- - -For a soft commit: - -[source,bash] ----- -curl -X POST -H 'Content-Type: application/json' \ - 'http://localhost:8983/api/collections/my_collection/update/json?softCommit=true' \ - --data-binary '[{"id":"1","title":"Doc 1"}]' ----- - -Alternatively, use `/update/xml` to issue an explicit commit command: - -[source,bash] ----- -curl -X POST -H 'Content-Type: application/xml' \ - 'http://localhost:8983/api/collections/my_collection/update/xml' \ - --data-binary '' ----- - -== Comparison with V1 Update Handlers - -The table below summarizes the key differences between the v1 and v2 update endpoints. - -[width="100%",options="header",] -|=== -|Feature |V1 |V2 -|Base path |`/solr/{collection}/update` |`/api/collections/{collection}/update` -|JSON documents |`/update` or `/update/json/docs` |`/update` with JSON update syntax, or `/update/json` for bare documents -|JSON update commands (commit/delete/optimize) |`/update` |`/update` -|XML updates |`/update` (via Content-Type) |`/update` (via Content-Type) or `/update/xml` -|CSV updates |`/update/csv` |`/update` (via Content-Type) or `/update/csv` -|Javabin updates |`/update` (via Content-Type) |`/update` (via Content-Type) or `/update/javabin` -|=== - -For full details on the v1 update handlers, see xref:indexing-with-update-handlers.adoc[]. From c783adb82b6397c1acf21defd10640e30551fdd6 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 17:31:57 -0400 Subject: [PATCH 34/40] Reorder and refactor the content. --- .../pages/indexing-with-update-handlers.adoc | 1173 ++++++++--------- 1 file changed, 550 insertions(+), 623 deletions(-) diff --git a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc index 1c875356a8df..019f818b011f 100644 --- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc +++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc @@ -20,404 +20,87 @@ Update handlers are request handlers designed to add, delete and update documents to the index. In addition to having plugins for importing rich documents (see xref:indexing-with-tika.adoc[]), Solr natively supports indexing structured documents in XML, CSV, and JSON. -NOTE: Solr also exposes update functionality via the xref:configuration-guide:v2-api.adoc[v2 API]. -The v2 update endpoints are documented in the <> section below. - -The recommended way to configure and use request handlers is with path based names that map to paths in the request URL. -However, request handlers can also be specified with the `qt` (query type) parameter if the xref:configuration-guide:requestdispatcher.adoc[`requestDispatcher`] is appropriately configured. -It is possible to access the same handler using more than one name, which can be useful if you wish to specify different sets of default options. - A single unified update request handler supports XML, CSV, JSON, and javabin update requests, delegating to the appropriate `ContentStreamLoader` based on the `Content-Type` of the xref:content-streams.adoc[ContentStream]. If you need to pre-process documents after they are loaded but before they are indexed (or even checked against the schema), Solr has document preprocessing plugins for Update Request Handlers, called xref:configuration-guide:update-request-processors.adoc[], which allow for default and custom configuration chains. -== UpdateRequestHandler Configuration - -The default configuration file has the update request handler configured by default. - -[source,xml] ----- - ----- - -== Versioned Update Responses - -Add the `versions=true` query parameter to an update request when the response must include the versions assigned to add or delete operations. -This parameter is supported by both the V1 update handlers and the V2 update endpoints. -It can be used with JSON, XML, CSV, Javabin, and CBOR update requests where the format is supported by the endpoint. -The `adds` field applies to document-ingest formats; `deletes` and `deleteByQuery` apply to formats that accept delete commands, such as JSON command updates, XML, and Javabin. - -See xref:partial-document-updates.adoc#optimistic-concurrency[Optimistic Concurrency] for more information about update versions. - -[tabs#versioned-update-response] -====== -V1 API:: -+ -==== -V1 returns alternating name/version values. -[source,json] ----- -{ - "responseHeader": { - "status": 0, - "QTime": 127 - }, - "adds": [ - "0002166313", - 123456789 - ], - "deletes": [ - "0002166314", - -123456790 - ], - "deleteByQuery": [ - "status:deleted", - -123456791 - ] -} ----- -==== - -V2 API:: -+ -==== -The v2 API returns typed entries: - -[source,json] ----- -{ - "responseHeader": { - "status": 0, - "QTime": 127 - }, - "adds": [ - { - "id": "0002166313", - "version": 123456789 - } - ], - "deletes": [ - { - "id": "0002166314", - "version": -123456790 - } - ], - "deleteByQuery": [ - { - "query": "status:deleted", - "version": -123456791 - } - ] -} ----- -==== -====== - -The status field will be non-zero in case of failure. -The response fields represent: - -* `adds`: Documents added and the versions assigned to them. -* `deletes`: Documents deleted and the versions assigned to their delete operations. -* `deleteByQuery`: Delete-by-query operations and the versions assigned to them. - -The version value is assigned by Solr. Delete versions are commonly negative. - -== XML Formatted Index Updates - -Index update commands can be sent as XML message to the update handler using `Content-type: application/xml` or `Content-type: text/xml`. - -=== Adding Documents - -The XML schema recognized by the update handler for adding documents is very straightforward: - -* The `` element introduces one more documents to be added. -* The `` element introduces the fields making up a document. -* The `` element presents the content for a specific field. - -For example: - -[source,xml] ----- - - - Patrick Eagar - Sports - 796.35 - 128 - - 12.40 - Summer of the all-rounder: Test and championship cricket in England 1982 - 0002166313 - 1982 - Collins - - - ... - - ----- - -The add command supports some optional attributes which may be specified. - -`commitWithin`:: -+ -[%autowidth,frame=none] -|=== -|Optional |Default: none -|=== -+ -Add the document within the specified number of milliseconds. - -`overwrite`:: -+ -[%autowidth,frame=none] -|=== -|Optional |Default: `true` -|=== -+ -Indicates if the unique key constraints should be checked to overwrite previous versions of the same document (see below). - -If the document schema defines a unique key, then by default an `/update` operation to add a document will overwrite (i.e., replace) any document in the index with the same unique key. -If no unique key has been defined, indexing performance is somewhat faster, as no check has to be made for an existing documents to replace. - -If you have a unique key field, but you feel confident that you can safely bypass the uniqueness check (e.g., you build your indexes in batch, and your indexing code guarantees it never adds the same document more than once) you can specify the `overwrite="false"` option when adding your documents. - -=== XML Update Commands - -==== Commit and Optimize During Updates - -The `` operation writes all documents loaded since the last commit to one or more segment files on the disk. -Before a commit has been issued, newly indexed content is not visible to searches. -The commit operation opens a new searcher, and triggers any event listeners that have been configured. - -Commits may be issued explicitly with a `` message, and can also be triggered from `` parameters in `solrconfig.xml`. - -The `` operation requests Solr to merge internal data structures. -For a large index, optimization will take some time to complete, but by merging many small segment files into larger segments, search performance may improve. -If you are using Solr's replication mechanism to distribute searches across many systems, be aware that after an optimize, a complete index will need to be transferred. - -WARNING: You should only consider using optimize on static indexes, i.e., indexes that can be optimized as part of the regular update process (say once-a-day updates). -Applications requiring NRT functionality should not use optimize. - -The `` and `` elements accept these optional attributes: - -`waitSearcher`:: -+ -[%autowidth,frame=none] -|=== -|Optional |Default: `true` -|=== -+ -Blocks until a new searcher is opened and registered as the main query searcher, making the changes visible. +== JSON Formatted Index Updates -`expungeDeletes`:: -+ -[%autowidth,frame=none] -|=== -|Optional |Default: `false` -|=== -+ -Merges segments that have more than 10% deleted docs, expunging the deleted documents in the process. -Resulting segments will respect `maxMergedSegmentMB`. -This option only applies in a `` operation. -+ -WARNING: `expungeDeletes` is less expensive than optimize, but the same warnings apply. +Solr can accept JSON that conforms to a defined structure, or can accept arbitrary JSON-formatted documents. +If sending arbitrarily formatted JSON, there are some additional parameters that need to be sent with the update request, described in the section xref:transforming-and-indexing-custom-json.adoc[]. -`maxSegments`:: -+ -[%autowidth,frame=none] -|=== -|Optional |Default: none -|=== -+ -Makes a best effort attempt to merge the segments down to no more than this number of segments but does not guarantee that the goal will be achieved. -Unless there is tangible evidence that optimizing to a small number of segments is beneficial, this parameter should be omitted and the default behavior accepted. -This option only applies in a `` operation. -Default is unlimited, resulting segments respect the `maxMergedSegmentMB` setting. +=== Solr-Style JSON -Here are examples of `` and `` using optional attributes: +JSON formatted update requests may be sent to Solr's `/update` handler using `Content-Type: application/json` or `Content-Type: text/json`. -[tabs#xml-commit-request] -====== -V1 API:: -+ -==== -[source,bash] ----- -curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d ' - - -' ----- -==== +JSON formatted updates can take 3 basic forms, described in depth below: -V2 API:: -+ -==== -[source,bash] ----- -curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d ' - - -' ----- -==== -====== +* <>, expressed as a top level JSON Object. +To differentiate this from a set of commands, the `json.command=false` request parameter is required. +* <>, expressed as a top level JSON Array containing a JSON Object per document. +* <>, expressed as a top level JSON Object (a Map). -==== Delete Operations +==== Adding a Single JSON Document -Documents can be deleted from the index in two ways. -"Delete by ID" deletes the document with the specified ID, and can be used only if a UniqueID field has been defined in the schema. -It doesn't work for child/nested docs. -"Delete by Query" deletes all documents matching a specified query, although `commitWithin` is ignored for a Delete by Query. -A single delete message can contain multiple delete operations. +The simplest way to add documents via JSON is to send each document individually as a JSON Object. -[tabs#xml-delete-request] +[tabs#json-single-document-request] ====== V1 API:: + ==== -[source,bash] ----- -curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d ' - - 0002166313 - 0031745983 - subject:sport - publisher:penguin -' ----- -==== - -V2 API:: -+ -==== -[source,bash] ----- -curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d ' - - 0002166313 - 0031745983 - subject:sport - publisher:penguin -' ----- -==== -====== - -[IMPORTANT] -==== - -Some queries, including many `{!join}` and `{!graph}` queries, are not supported in delete operations and will return a 400 error. - -Users interested in using the Join query parser in a Delete By Query may do so by specifying a `score` parameter with the value "none" (and ensuring no `method` parameter is provided). -See the section on the xref:query-guide:join-query-parser.adoc[] for more details on the `score` parameter and its usage. - -==== - -==== Rollback Operations - -The rollback command rolls back all add and deletes made to the index since the last commit. -It neither calls any event listeners nor creates a new searcher. -Its syntax is simple: ``. - -The same command can be sent through either API: +The V1 document-only endpoint is `/update/json/docs`: -[tabs#xml-rollback-request] -====== -V1 API:: -+ -==== [source,bash] ---- -curl http://localhost:8983/solr/my_collection/update \ - -H "Content-Type: text/xml" -d '' +curl 'http://localhost:8983/solr/my_collection/update/json/docs' -H 'Content-Type: application/json' -d ' +{ + "id": "1", + "title": "Doc 1" +}' ---- ==== V2 API:: + ==== -[source,bash] ----- -curl http://localhost:8983/api/collections/my_collection/update/xml \ - -H "Content-Type: text/xml" -d '' ----- -==== -====== - -==== Grouping Operations - -You can post several commands in a single XML file by grouping them with the surrounding `` element. - -[tabs#xml-grouped-update-request] -====== -V1 API:: -+ -==== -[source,bash] ----- -curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d ' - - - - - - - - - 0002166313 - -' ----- -==== +The V2 document-only endpoint is `/update/json`: -V2 API:: -+ -==== [source,bash] ---- -curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d ' - - - - - - - - - 0002166313 - -' +curl 'http://localhost:8983/api/collections/my_collection/update/json' -H 'Content-Type: application/json' -d ' +{ + "id": "1", + "title": "Doc 1" +}' ---- ==== ====== -=== Using curl to Perform Updates +==== Adding Multiple JSON Documents -You can use the `curl` utility to perform any of the above commands, using its `-d` option to append the XML message to the `curl` command, and generating a HTTP POST request. -The same XML update can be sent through either API: +Adding multiple documents at one time via JSON can be done via a JSON Array of JSON Objects, where each object represents a document: -[tabs#xml-update-request] +[tabs#json-multiple-document-request] ====== V1 API:: + ==== [source,bash] ---- -curl http://localhost:8983/solr/my_collection/update?versions=true -H "Content-Type: text/xml" -d ' - - - Patrick Eagar - Sports - 796.35 - 0002166313 - 1982 - Collins - -' +curl 'http://localhost:8983/solr/my_collection/update' -H 'Content-Type: application/json' -d ' +[ + { + "id": "1", + "title": "Doc 1" + }, + { + "id": "2", + "title": "Doc 2" + } +]' ---- ==== @@ -426,31 +109,31 @@ V2 API:: ==== [source,bash] ---- -curl http://localhost:8983/api/collections/my_collection/update/xml?versions=true -H "Content-Type: text/xml" -d ' - - - Patrick Eagar - Sports - 796.35 - 0002166313 - 1982 - Collins - -' +curl 'http://localhost:8983/api/collections/my_collection/update' -H 'Content-Type: application/json' -d ' +[ + { + "id": "1", + "title": "Doc 1" + }, + { + "id": "2", + "title": "Doc 2" + } +]' ---- ==== ====== -For posting XML messages contained in a file, you can use the alternative form: +A JSON file can be submitted in the same way: -[tabs#xml-file-update-request] +[tabs#json-file-request] ====== V1 API:: + ==== [source,bash] ---- -curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d @myfile.xml +curl 'http://localhost:8983/solr/techproducts/update?commit=true' -H 'Content-type:application/json' -d @example/exampledocs/books.json ---- ==== @@ -459,200 +142,227 @@ V2 API:: ==== [source,bash] ---- -curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d @myfile.xml +curl 'http://localhost:8983/api/collections/techproducts/update?commit=true' -H 'Content-type:application/json' -d @example/exampledocs/books.json ---- ==== ====== -The approach above works well, but using the `-d` option causes `curl` to load the whole `myfile.xml` into memory before posting it to server. -This may be problematic when dealing with multi-gigabyte files. -This alternative `curl` command performs equivalent operations but with minimal `curl` memory usage: +==== Sending JSON Update Commands + +In general, the JSON update syntax supports all of the update commands that the XML update handler supports, through a straightforward mapping. +Multiple commands, adding and deleting documents, may be contained in one message: -[tabs#xml-file-streaming-request] +[tabs#json-command-request] ====== V1 API:: + ==== -[source,bash] +[source,bash,subs="verbatim,callouts"] ---- -curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -T "myfile.xml" -X POST +curl 'http://localhost:8983/solr/my_collection/update' -H 'Content-Type: application/json' -d ' +{ + "add": { + "doc": { + "id": "DOC1", + "my_field": 2.3, + "my_multivalued_field": [ "aaa", "bbb" ] --<1> + } + }, + "add": { + "commitWithin": 5000, --<2> + "overwrite": false, --<3> + "doc": { + "f1": "v1", --<4> + "f1": "v2" + } + }, + + "commit": {}, + "optimize": { "waitSearcher":false }, + + "delete": { "id":"ID" }, --<5> + "delete": { "query":"QUERY" } --<6> +}' ---- ==== V2 API:: + ==== -[source,bash] +[source,bash,subs="verbatim,callouts"] ---- -curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -T "myfile.xml" -X POST +curl 'http://localhost:8983/api/collections/my_collection/update' -H 'Content-Type: application/json' -d ' +{ + "add": { + "doc": { + "id": "DOC1", + "my_field": 2.3, + "my_multivalued_field": [ "aaa", "bbb" ] --<1> + } + }, + "add": { + "commitWithin": 5000, --<2> + "overwrite": false, --<3> + "doc": { + "f1": "v1", --<4> + "f1": "v2" + } + }, + + "commit": {}, + "optimize": { "waitSearcher":false }, + + "delete": { "id":"ID" }, --<5> + "delete": { "query":"QUERY" } --<6> +}' ---- ==== ====== +<1> Can use an array for a multi-valued field +<2> Commit this document within 5 seconds +<3> Don't check for existing documents with the same uniqueKey +<4> Can use repeated keys for a multi-valued field +<5> Delete by ID (uniqueKey field) +<6> Delete by Query -=== Using XSLT to Transform XML Index Updates - -NOTE: The XSLT update handler is V1-only. V2 does not provide a configurable equivalent for custom request handlers such as `/update/xslt`; use the V1 endpoint for these requests. +As with other update handlers, parameters such as `commit`, `commitWithin`, `optimize`, and `overwrite` may be specified in the URL instead of in the body of the message. -The xref:configuration-guide:script-update-processor.adoc[Scripting module] provides a separate XSLT Update Request Handler that allows you to index any arbitrary XML by using the `` parameter to apply an https://en.wikipedia.org/wiki/XSLT[XSL transformation]. -You must have an XSLT stylesheet in the `conf/xslt` directory of your xref:configuration-guide:config-sets.adoc[configset] that can transform the incoming data to the expected `` format, and use the `tr` parameter to specify the name of that stylesheet. +The JSON update format allows for a simple delete-by-id. +The value of a `delete` can be an array which contains a list of zero or more specific document id's (not a range) to be deleted. +For example, a single document: -You will need to enable the xref:configuration-guide:script-update-processor.adoc#module[scripting Module] before using this feature. +[source,json] +---- +{ "delete":"myid" } +---- -=== tr Parameter +Or a list of document IDs: -The XSLT Update Request Handler accepts the `tr` parameter, which identifies the XML transformation to use. -The transformation must be found in the Solr `conf/xslt` directory. +[source,json] +---- +{ "delete":["id1","id2"] } +---- -=== XSLT Configuration +Note: Delete-by-id doesn't work for child/nested docs. -The example below, from the `sample_techproducts_configs` xref:configuration-guide:config-sets.adoc[configset] in the Solr distribution, shows how the XSLT Update Request Handler is configured. +You can also specify `\_version_` with each "delete": -[source,xml] +[source,json] ---- - - - 5 - +{ + "delete":"id":50, + "_version_":12345 +} ---- -A value of 5 for `xsltCacheLifetimeSeconds` is good for development, to see XSLT changes quickly. -For production you probably want a much higher value. +You can specify the version of deletes in the body of the update request as well. -=== XSLT Update Example +=== JSON Update Convenience Paths -Here is the `sample_techproducts_configs/conf/xslt/updateXml.xsl` XSL file for converting standard Solr XML output to the Solr expected `` format: +The V1 `/update/json` endpoint is a specialized Solr request path designed for clients that cannot easily set the required `Content-Type: application/json` header, as this route automatically forces Solr to treat the incoming payload as JSON. +The V1 `/update/json/docs` path additionally forces document-only processing through `stream.contentType=application/json` and `json.command=false`. -[source,xml] ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ----- +In V2, use the general `/api/collections/\{collection\}/update` or `/api/cores/\{core\}/update` endpoint with `Content-Type: application/json` for JSON update commands. +Use `/api/collections/\{collection\}/update/json` or `/api/cores/\{core\}/update/json` for document-only JSON requests. -This stylesheet transforms Solr's XML search result format into Solr's Update XML syntax. -One example usage would be to copy a Solr 1.3 index (which does not have CSV response writer) into a format which can be indexed into another Solr file (provided that all fields are stored): +=== Custom JSON Documents -[source,bash] ----- -$ curl "http://localhost:8983/solr/techproducts/select?q=ipod&fl=id,cat,name,popularity,price,score&wt=xml" -o standard_solr_xml_format.xml +Solr can support custom JSON. +This is covered in the section xref:transforming-and-indexing-custom-json.adoc[]. -$ curl "http://localhost:8983/solr/techproducts/update/xslt?commit=true&tr=updateXml.xsl" -H "Content-Type: text/xml" -d @standard_solr_xml_format.xml ----- +== XML Formatted Index Updates -NOTE: You can see the opposite export/import cycle using the `tr` parameter in the xref:query-guide:response-writers.adoc#xslt-writer-example[Response Writer XSLT example]. +Index update commands can be sent as XML message to the update handler using `Content-type: application/xml` or `Content-type: text/xml`. -== JSON Formatted Index Updates +=== Adding Documents -Solr can accept JSON that conforms to a defined structure, or can accept arbitrary JSON-formatted documents. -If sending arbitrarily formatted JSON, there are some additional parameters that need to be sent with the update request, described in the section xref:transforming-and-indexing-custom-json.adoc[]. +The XML schema recognized by the update handler for adding documents is very straightforward: -=== Solr-Style JSON +* The `` element introduces one more documents to be added. +* The `` element introduces the fields making up a document. +* The `` element presents the content for a specific field. -JSON formatted update requests may be sent to Solr's `/update` handler using `Content-Type: application/json` or `Content-Type: text/json`. +For example: -JSON formatted updates can take 3 basic forms, described in depth below: +[source,xml] +---- + + + Patrick Eagar + Sports + 796.35 + 128 + + 12.40 + Summer of the all-rounder: Test and championship cricket in England 1982 + 0002166313 + 1982 + Collins + + + ... + + +---- -* <>, expressed as a top level JSON Object. -To differentiate this from a set of commands, the `json.command=false` request parameter is required. -* <>, expressed as a top level JSON Array containing a JSON Object per document. -* <>, expressed as a top level JSON Object (a Map). +The add command supports the shared `commitWithin` and `overwrite` options described in the <> section. +The XML syntax places these options on the `` element. + +=== XML Update Commands -==== Adding a Single JSON Document +The XML update syntax supports commit and optimize commands. Their behavior and options are described in the <> section above. -The simplest way to add documents via JSON is to send each document individually as a JSON Object. +Here are examples of `` and `` using optional attributes: -[tabs#json-single-document-request] +[tabs#xml-commit-request] ====== V1 API:: + ==== -The V1 document-only endpoint is `/update/json/docs`: - [source,bash] ---- -curl 'http://localhost:8983/solr/my_collection/update/json/docs' -H 'Content-Type: application/json' -d ' -{ - "id": "1", - "title": "Doc 1" -}' +curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d ' + + +' ---- ==== V2 API:: + ==== -The V2 document-only endpoint is `/update/json`: - [source,bash] ---- -curl 'http://localhost:8983/api/collections/my_collection/update/json' -H 'Content-Type: application/json' -d ' -{ - "id": "1", - "title": "Doc 1" -}' +curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d ' + + +' ---- ==== ====== -==== Adding Multiple JSON Documents +==== Delete Operations -Adding multiple documents at one time via JSON can be done via a JSON Array of JSON Objects, where each object represents a document: +Documents can be deleted from the index in two ways. +"Delete by ID" deletes the document with the specified ID, and can be used only if a UniqueID field has been defined in the schema. +It doesn't work for child/nested docs. +"Delete by Query" deletes all documents matching a specified query, although `commitWithin` is ignored for a Delete by Query. +A single delete message can contain multiple delete operations. -[tabs#json-multiple-document-request] +[tabs#xml-delete-request] ====== V1 API:: + ==== [source,bash] ---- -curl 'http://localhost:8983/solr/my_collection/update' -H 'Content-Type: application/json' -d ' -[ - { - "id": "1", - "title": "Doc 1" - }, - { - "id": "2", - "title": "Doc 2" - } -]' +curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d ' + + 0002166313 + 0031745983 + subject:sport + publisher:penguin +' ---- ==== @@ -661,31 +371,44 @@ V2 API:: ==== [source,bash] ---- -curl 'http://localhost:8983/api/collections/my_collection/update' -H 'Content-Type: application/json' -d ' -[ - { - "id": "1", - "title": "Doc 1" - }, - { - "id": "2", - "title": "Doc 2" - } -]' +curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d ' + + 0002166313 + 0031745983 + subject:sport + publisher:penguin +' ---- ==== ====== -A JSON file can be submitted in the same way: +[IMPORTANT] +==== -[tabs#json-file-request] +Some queries, including many `{!join}` and `{!graph}` queries, are not supported in delete operations and will return a 400 error. + +Users interested in using the Join query parser in a Delete By Query may do so by specifying a `score` parameter with the value "none" (and ensuring no `method` parameter is provided). +See the section on the xref:query-guide:join-query-parser.adoc[] for more details on the `score` parameter and its usage. + +==== + +==== Rollback Operations + +The rollback command rolls back all add and deletes made to the index since the last commit. +It neither calls any event listeners nor creates a new searcher. +Its syntax is simple: ``. + +The same command can be sent through either API: + +[tabs#xml-rollback-request] ====== V1 API:: + ==== [source,bash] ---- -curl 'http://localhost:8983/solr/techproducts/update?commit=true' -H 'Content-type:application/json' -d @example/exampledocs/books.json +curl http://localhost:8983/solr/my_collection/update \ + -H "Content-Type: text/xml" -d '' ---- ==== @@ -694,134 +417,147 @@ V2 API:: ==== [source,bash] ---- -curl 'http://localhost:8983/api/collections/techproducts/update?commit=true' -H 'Content-type:application/json' -d @example/exampledocs/books.json +curl http://localhost:8983/api/collections/my_collection/update/xml \ + -H "Content-Type: text/xml" -d '' ---- ==== ====== -==== Sending JSON Update Commands +==== Grouping Operations -In general, the JSON update syntax supports all of the update commands that the XML update handler supports, through a straightforward mapping. -Multiple commands, adding and deleting documents, may be contained in one message: +You can post several commands in a single XML file by grouping them with the surrounding `` element. -[tabs#json-command-request] +[tabs#xml-grouped-update-request] ====== V1 API:: + ==== -[source,bash,subs="verbatim,callouts"] +[source,bash] ---- -curl 'http://localhost:8983/solr/my_collection/update' -H 'Content-Type: application/json' -d ' -{ - "add": { - "doc": { - "id": "DOC1", - "my_field": 2.3, - "my_multivalued_field": [ "aaa", "bbb" ] --<1> - } - }, - "add": { - "commitWithin": 5000, --<2> - "overwrite": false, --<3> - "doc": { - "f1": "v1", --<4> - "f1": "v2" - } - }, - - "commit": {}, - "optimize": { "waitSearcher":false }, - - "delete": { "id":"ID" }, --<5> - "delete": { "query":"QUERY" } --<6> -}' +curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d ' + + + + + + + + + 0002166313 + +' ---- ==== V2 API:: + ==== -[source,bash,subs="verbatim,callouts"] +[source,bash] ---- -curl 'http://localhost:8983/api/collections/my_collection/update' -H 'Content-Type: application/json' -d ' -{ - "add": { - "doc": { - "id": "DOC1", - "my_field": 2.3, - "my_multivalued_field": [ "aaa", "bbb" ] --<1> - } - }, - "add": { - "commitWithin": 5000, --<2> - "overwrite": false, --<3> - "doc": { - "f1": "v1", --<4> - "f1": "v2" - } - }, - - "commit": {}, - "optimize": { "waitSearcher":false }, - - "delete": { "id":"ID" }, --<5> - "delete": { "query":"QUERY" } --<6> -}' +curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d ' + + + + + + + + + 0002166313 + +' ---- ==== ====== -<1> Can use an array for a multi-valued field -<2> Commit this document within 5 seconds -<3> Don't check for existing documents with the same uniqueKey -<4> Can use repeated keys for a multi-valued field -<5> Delete by ID (uniqueKey field) -<6> Delete by Query -As with other update handlers, parameters such as `commit`, `commitWithin`, `optimize`, and `overwrite` may be specified in the URL instead of in the body of the message. +=== Using XSLT to Transform XML Index Updates -The JSON update format allows for a simple delete-by-id. -The value of a `delete` can be an array which contains a list of zero or more specific document id's (not a range) to be deleted. -For example, a single document: +NOTE: The XSLT update handler is V1-only. V2 does not provide a configurable equivalent for custom request handlers such as `/update/xslt`; use the V1 endpoint for these requests. -[source,json] +The xref:configuration-guide:script-update-processor.adoc[Scripting module] provides a separate XSLT Update Request Handler that allows you to index any arbitrary XML by using the `` parameter to apply an https://en.wikipedia.org/wiki/XSLT[XSL transformation]. +You must have an XSLT stylesheet in the `conf/xslt` directory of your xref:configuration-guide:config-sets.adoc[configset] that can transform the incoming data to the expected `` format, and use the `tr` parameter to specify the name of that stylesheet. + +You will need to enable the xref:configuration-guide:script-update-processor.adoc#module[scripting Module] before using this feature. + +=== tr Parameter + +The XSLT Update Request Handler accepts the `tr` parameter, which identifies the XML transformation to use. +The transformation must be found in the Solr `conf/xslt` directory. + +=== XSLT Configuration + +The example below, from the `sample_techproducts_configs` xref:configuration-guide:config-sets.adoc[configset] in the Solr distribution, shows how the XSLT Update Request Handler is configured. + +[source,xml] ---- -{ "delete":"myid" } + + + 5 + +---- + +A value of 5 for `xsltCacheLifetimeSeconds` is good for development, to see XSLT changes quickly. +For production you probably want a much higher value. + +=== XSLT Update Example + +Here is the `sample_techproducts_configs/conf/xslt/updateXml.xsl` XSL file for converting standard Solr XML output to the Solr expected `` format: + +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ---- -Or a list of document IDs: +This stylesheet transforms Solr's XML search result format into Solr's Update XML syntax. +One example usage would be to copy a Solr 1.3 index (which does not have CSV response writer) into a format which can be indexed into another Solr file (provided that all fields are stored): -[source,json] ----- -{ "delete":["id1","id2"] } +[source,bash] ---- +curl 'http://localhost:8983/solr/techproducts/select?q=ipod&fl=id,cat,name,popularity,price,score&wt=xml' -o standard_solr_xml_format.xml -Note: Delete-by-id doesn't work for child/nested docs. - -You can also specify `\_version_` with each "delete": - -[source,json] ----- -{ - "delete":"id":50, - "_version_":12345 -} +curl 'http://localhost:8983/solr/techproducts/update/xslt?commit=true&tr=updateXml.xsl' -H 'Content-Type: text/xml' -d @standard_solr_xml_format.xml ---- -You can specify the version of deletes in the body of the update request as well. - -=== JSON Update Convenience Paths - -The V1 `/update/json` endpoint is a specialized Solr request path designed for clients that cannot easily set the required `Content-Type: application/json` header, as this route automatically forces Solr to treat the incoming payload as JSON. -The V1 `/update/json/docs` path additionally forces document-only processing through `stream.contentType=application/json` and `json.command=false`. - -In V2, use the general `/api/collections/\{collection\}/update` or `/api/cores/\{core\}/update` endpoint with `Content-Type: application/json` for JSON update commands. -Use `/api/collections/\{collection\}/update/json` or `/api/cores/\{core\}/update/json` for document-only JSON requests. - -=== Custom JSON Documents - -Solr can support custom JSON. -This is covered in the section xref:transforming-and-indexing-custom-json.adoc[]. - +NOTE: You can see the opposite export/import cycle using the `tr` parameter in the xref:query-guide:response-writers.adoc#xslt-writer-example[Response Writer XSLT example]. == CSV Formatted Index Updates @@ -1000,17 +736,7 @@ Example: `map=left:right` or `f.subject.map=history:bunk` If `true`, split a field into multiple values by a separate parser. This parameter is used on a per-field basis, for example `f.FIELD_NAME_GOES_HERE.split=true`. -`overwrite`:: -+ -[%autowidth,frame=none] -|=== -|Optional |Default: `true` -|=== -+ -If `true`, check for and overwrite duplicate documents, based on the uniqueKey field declared in the Solr schema. -If you know the documents you are indexing do not contain any duplicates then you may see a considerable speed up setting this to `false`. -+ -This parameter is global. +`overwrite` is a common update option; see <> for its behavior. `commit`:: + @@ -1022,17 +748,7 @@ This parameter is global. Issues a commit after the data has been ingested. This parameter is global. -`commitWithin`:: -+ -[%autowidth,frame=none] -|=== -|Optional |Default: none -|=== -+ -Add the document within the specified number of milliseconds. -This parameter is global. -+ -Example: `commitWithin=10000` +`commitWithin` is a common update option; see <> for its behavior. `rowid`:: + @@ -1097,8 +813,219 @@ curl 'http://localhost:8983/api/collections/my_collection/update/csv?commit=true The `/update/csv` endpoint is a specialized Solr request path designed for importing CSV data. You use it primarily when your client application or script cannot easily set the required `Content-Type: text/csv` header, as this route automatically forces Solr to treat all incoming payloads as CSV data without needing extra parameter configuration. +== Commit and Optimize + +The `` operation writes all documents loaded since the last commit to one or more segment files on the disk. +Before a commit has been issued, newly indexed content is not visible to searches. +The commit operation opens a new searcher, and triggers any event listeners that have been configured. + +Commits may be issued explicitly with a `` message, and can also be triggered from `` parameters in `solrconfig.xml`. + +The `` operation requests Solr to merge internal data structures. +For a large index, optimization will take some time to complete, but by merging many small segment files into larger segments, search performance may improve. +If you are using Solr's replication mechanism to distribute searches across many systems, be aware that after an optimize, a complete index will need to be transferred. + +WARNING: You should only consider using optimize on static indexes, i.e., indexes that can be optimized as part of the regular update process (say once-a-day updates). +Applications requiring NRT functionality should not use optimize. + +The `` and `` elements accept these optional attributes: + +`waitSearcher`:: ++ +[%autowidth,frame=none] +|=== +|Optional |Default: `true` +|=== ++ +Blocks until a new searcher is opened and registered as the main query searcher, making the changes visible. + +`expungeDeletes`:: ++ +[%autowidth,frame=none] +|=== +|Optional |Default: `false` +|=== ++ +Merges segments that have more than 10% deleted docs, expunging the deleted documents in the process. +Resulting segments will respect `maxMergedSegmentMB`. +This option only applies in a `` operation. ++ +WARNING: `expungeDeletes` is less expensive than optimize, but the same warnings apply. + +`maxSegments`:: ++ +[%autowidth,frame=none] +|=== +|Optional |Default: none +|=== ++ +Makes a best effort attempt to merge the segments down to no more than this number of segments but does not guarantee that the goal will be achieved. +Unless there is tangible evidence that optimizing to a small number of segments is beneficial, this parameter should be omitted and the default behavior accepted. +This option only applies in a `` operation. +Default is unlimited, resulting segments respect the `maxMergedSegmentMB` setting. + +== Common Update Options + +These options apply to document update requests in JSON, XML, and CSV formats. +They can be supplied as URL parameters; formats that support command bodies may also provide them in the body using their format-specific syntax. + +`commitWithin`:: ++ +Requests that Solr commit the update within the specified number of milliseconds. + +`overwrite`:: ++ +Controls whether Solr checks the unique key and replaces an existing document with the same key. +The default is `true`. +Set it to `false` when the input is known not to contain duplicate unique keys and replacing existing documents is unnecessary. + +== Versioned Update Responses + +Add the `versions=true` query parameter to an update request when the response must include the versions assigned to add or delete operations. +This parameter is supported by both the V1 update handlers and the V2 update endpoints. +It can be used with JSON, XML, CSV, Javabin, and CBOR update requests where the format is supported by the endpoint. +The `adds` field applies to document-ingest formats; `deletes` and `deleteByQuery` apply to formats that accept delete commands, such as JSON command updates, XML, and Javabin. + +See xref:partial-document-updates.adoc#optimistic-concurrency[Optimistic Concurrency] for more information about update versions. + +[tabs#versioned-update-response] +====== +V1 API:: ++ +==== +V1 returns alternating name/version values. +[source,json] +---- +{ + "responseHeader": { + "status": 0, + "QTime": 127 + }, + "adds": [ + "0002166313", + 123456789 + ], + "deletes": [ + "0002166314", + -123456790 + ], + "deleteByQuery": [ + "status:deleted", + -123456791 + ] +} +---- +==== + +V2 API:: ++ +==== +The v2 API returns typed entries: + +[source,json] +---- +{ + "responseHeader": { + "status": 0, + "QTime": 127 + }, + "adds": [ + { + "id": "0002166313", + "version": 123456789 + } + ], + "deletes": [ + { + "id": "0002166314", + "version": -123456790 + } + ], + "deleteByQuery": [ + { + "query": "status:deleted", + "version": -123456791 + } + ] +} +---- +==== +====== + +The status field will be non-zero in case of failure. + +The response fields represent: + +* `adds`: Documents added and the versions assigned to them. +* `deletes`: Documents deleted and the versions assigned to their delete operations. +* `deleteByQuery`: Delete-by-query operations and the versions assigned to them. + +The version value is assigned by Solr. Delete versions are commonly negative. + == Javabin Document Indexing Javabin is Solr's native binary update format and is primarily intended for SolrJ clients. V1 clients send Javabin to `/update` with `Content-Type: application/javabin`. V2 clients can use `/update/javabin` or the general `/update` endpoint with the same content type. + +== UpdateRequestHandler Configuration + +The default configuration file has the update request handler configured by default. + +The recommended way to configure and use request handlers is with path-based names that map directly to paths in the request URL. +You can register a handler at more than one path when you need different defaults for different uses. + +[source,xml] +---- + +---- + +== Posting Large Update Files + +Large update files can be posted through either API. The examples below use XML; use the corresponding content type and endpoint for CSV, Javabin, or another supported format. +Using `-d @myfile.xml` causes `curl` to load the whole file into memory before posting it to the server. +This may be problematic for multi-gigabyte files. + +[tabs#large-file-update-request] +====== +V1 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -d @myfile.xml +---- +==== + +V2 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -d @myfile.xml +---- +==== +====== + +For minimal `curl` memory usage, stream the file with `-T`: + +[tabs#large-file-streaming-request] +====== +V1 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/solr/my_collection/update -H "Content-Type: text/xml" -T "myfile.xml" -X POST +---- +==== + +V2 API:: ++ +==== +[source,bash] +---- +curl http://localhost:8983/api/collections/my_collection/update/xml -H "Content-Type: text/xml" -T "myfile.xml" -X POST +---- +==== +====== From ed6bc2c26c163a2bab77cf55a5d2fa2b20f15f70 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 17:52:52 -0400 Subject: [PATCH 35/40] Updates to the transforming page for v2, and a test to ensure it works --- .../solr/handler/admin/api/UpdateAPITest.java | 48 +++ ...transforming-and-indexing-custom-json.adoc | 325 ++++-------------- 2 files changed, 119 insertions(+), 254 deletions(-) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java index 88d43e2ee1a8..3387429204af 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/UpdateAPITest.java @@ -56,6 +56,7 @@ public class UpdateAPITest extends SolrTestCase { @ClassRule public static SolrJettyTestRule solrTestRule = new SolrJettyTestRule(); private static final String CORE_NAME = "update-api-test"; + private static final String CUSTOM_JSON_CORE_NAME = "custom-json-update-api-test"; @BeforeClass public static void beforeClass() throws Exception { @@ -66,6 +67,12 @@ public static void beforeClass() throws Exception { .newCollection(CORE_NAME) .withConfigSet(ExternalPaths.TECHPRODUCTS_CONFIGSET) .create(); + // can't use techproducts config because it enables srcField, which is incompatible with nested + // split=/exams requests + solrTestRule + .newCollection(CUSTOM_JSON_CORE_NAME) + .withConfigSet(ExternalPaths.DEFAULT_CONFIGSET) + .create(); } @Test @@ -86,6 +93,36 @@ public void testV1AndV2GenericUpdateParityAcrossFormats() throws Exception { } } + @Test + public void testV1AndV2CustomJsonTransformParity() throws Exception { + final SolrClient client = solrTestRule.getSolrClient(CUSTOM_JSON_CORE_NAME); + final String payload = "{\"exams\":[{\"id\":\"custom-json-v1\",\"name\":\"V1 document\"}]}"; + + final ModifiableSolrParams params = new ModifiableSolrParams(); + params.set("split", "/exams"); + params.add("f", "id:/exams/id"); + params.add("f", "name_s:/exams/name"); + params.set("commit", true); + + final GenericSolrRequest v1Request = + new GenericSolrRequest(SolrRequest.METHOD.POST, "/update/json/docs", params); + v1Request.setRequiresCollection(true); + v1Request.setContentWriter( + new RequestWriter.StringPayloadContentWriter(payload, "application/json")); + client.request(v1Request, CUSTOM_JSON_CORE_NAME); + + final String v2Payload = payload.replace("custom-json-v1", "custom-json-v2"); + final GenericV2SolrRequest v2Request = + new GenericV2SolrRequest( + SolrRequest.METHOD.POST, "/cores/" + CUSTOM_JSON_CORE_NAME + "/update/json", params); + v2Request.setContentWriter( + new RequestWriter.StringPayloadContentWriter(v2Payload, "application/json")); + client.request(v2Request); + + assertIndexedField(client, CUSTOM_JSON_CORE_NAME, "custom-json-v1", "name_s", "V1 document"); + assertIndexedField(client, CUSTOM_JSON_CORE_NAME, "custom-json-v2", "name_s", "V1 document"); + } + @Test public void testUpdateJsonViaV2Api() throws Exception { final SolrClient client = solrTestRule.getSolrClient(CORE_NAME); @@ -273,6 +310,17 @@ private static void assertIndexed(SolrClient client, String id) throws Exception assertEquals(id, 1, queryResponse.getResults().getNumFound()); } + private static void assertIndexedField( + SolrClient client, String collection, String id, String field, String value) + throws Exception { + final ModifiableSolrParams queryParams = new ModifiableSolrParams(); + queryParams.set("q", "id:" + id); + queryParams.set("fl", field); + final QueryResponse queryResponse = new QueryRequest(queryParams).process(client, collection); + assertEquals(1, queryResponse.getResults().getNumFound()); + assertEquals(value, queryResponse.getResults().get(0).getFieldValue(field)); + } + private enum UpdateFormat { JSON("application/json") { @Override 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..db3e75fc1336 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 @@ -20,7 +20,7 @@ If you have JSON documents that you would like to index without transforming them into Solr's structure, you can add them to Solr by including some parameters with the update request. These parameters provide information on how to split a single JSON file into multiple Solr documents and how to map fields to Solr's schema. -One or more valid JSON documents can be sent to the `/update/json/docs` path with the configuration params. +In V1, one or more valid JSON documents can be sent to `/update/json/docs` with the configuration parameters. In V2, use the document-only `/update/json` endpoint. == Mapping Parameters @@ -115,7 +115,8 @@ curl 'http://localhost:8983/solr/techproducts/update/json/docs'\ '&f=subject:/exams/subject'\ '&f=test:/exams/test'\ '&f=marks:/exams/marks'\ - -H 'Content-type:application/json' -d ' + -H 'Content-Type: application/json' \ + -d ' { "first": "John", "last": "Doe", @@ -134,44 +135,12 @@ curl 'http://localhost:8983/solr/techproducts/update/json/docs'\ ---- ==== -V2 API User-Managed / Standalone Solr:: +V2 API:: + ==== [source,bash] ---- -curl 'http://localhost:8983/api/cores/techproducts/update/json/docs'\ -'?split=/exams'\ -'&f=first:/first'\ -'&f=last:/last'\ -'&f=grade:/grade'\ -'&f=subject:/exams/subject'\ -'&f=test:/exams/test'\ -'&f=marks:/exams/marks'\ - -H 'Content-type:application/json' -d ' -{ - "first": "John", - "last": "Doe", - "grade": 8, - "exams": [ - { - "subject": "Maths", - "test" : "term1", - "marks" : 90}, - { - "subject": "Biology", - "test" : "term1", - "marks" : 86} - ] -}' ----- -==== - -V2 API SolrCloud:: -+ -==== -[source,bash] ----- -curl 'http://localhost:8983/api/collections/techproducts/update/json/docs'\ +curl 'http://localhost:8983/api/collections/techproducts/update/json'\ '?split=/exams'\ '&f=first:/first'\ '&f=last:/last'\ @@ -179,7 +148,8 @@ curl 'http://localhost:8983/api/collections/techproducts/update/json/docs'\ '&f=subject:/exams/subject'\ '&f=test:/exams/test'\ '&f=marks:/exams/marks'\ - -H 'Content-type:application/json' -d ' + -H 'Content-Type: application/json' \ + -d ' { "first": "John", "last": "Doe", @@ -242,7 +212,8 @@ curl 'http://localhost:8983/solr/techproducts/update/json/docs'\ '&f=/exams/subject'\ '&f=/exams/test'\ '&f=/exams/marks'\ - -H 'Content-type:application/json' -d ' + -H 'Content-Type: application/json' \ + -d ' { "first": "John", "last": "Doe", @@ -261,44 +232,12 @@ curl 'http://localhost:8983/solr/techproducts/update/json/docs'\ ---- ==== -V2 API User-Managed / Standalone Solr:: +V2 API:: + ==== [source,bash] ---- -curl 'http://localhost:8983/api/cores/techproducts/update/json/docs'\ -'?split=/exams'\ -'&f=/first'\ -'&f=/last'\ -'&f=/grade'\ -'&f=/exams/subject'\ -'&f=/exams/test'\ -'&f=/exams/marks'\ - -H 'Content-type:application/json' -d ' -{ - "first": "John", - "last": "Doe", - "grade": 8, - "exams": [ - { - "subject": "Maths", - "test" : "term1", - "marks" : 90}, - { - "subject": "Biology", - "test" : "term1", - "marks" : 86} - ] -}' ----- -==== - -V2 API SolrCloud:: -+ -==== -[source,bash] ----- -curl 'http://localhost:8983/api/collections/techproducts/update/json/docs'\ +curl 'http://localhost:8983/api/collections/techproducts/update/json'\ '?split=/exams'\ '&f=/first'\ '&f=/last'\ @@ -306,7 +245,8 @@ curl 'http://localhost:8983/api/collections/techproducts/update/json/docs'\ '&f=/exams/subject'\ '&f=/exams/test'\ '&f=/exams/marks'\ - -H 'Content-type:application/json' -d ' + -H 'Content-Type: application/json' \ + -d ' { "first": "John", "last": "Doe", @@ -350,7 +290,9 @@ V1 API:: ==== [source,bash] ---- - curl http://localhost:8983/solr/techproducts/config/params -H 'Content-type:application/json' -d '{ +curl http://localhost:8983/solr/techproducts/config/params \ + -H 'Content-Type: application/json' \ + -d '{ "set": { "my_params": { "split": "/exams", @@ -359,26 +301,14 @@ V1 API:: ---- ==== -V2 API User-Managed / Standalone Solr:: +V2 API:: + ==== [source,bash] ---- -curl http://localhost:8983/api/cores/techproducts/config/params -H 'Content-type:application/json' -d '{ - "set": { - "my_params": { - "split": "/exams", - "f": ["first:/first","last:/last","grade:/grade","subject:/exams/subject","test:/exams/test"] - }}}' ----- -==== - -V2 API SolrCloud:: -+ -==== -[source,bash] ----- -curl http://localhost:8983/api/collections/techproducts/config/params -H 'Content-type:application/json' -d '{ +curl http://localhost:8983/api/collections/techproducts/config/params \ + -H 'Content-Type: application/json' \ + -d '{ "set": { "my_params": { "split": "/exams", @@ -397,31 +327,10 @@ V1 API:: ==== [source,bash] ---- -curl 'http://localhost:8983/solr/techproducts/update/json/docs?useParams=my_params' -H 'Content-type:application/json' -d '{ - "first": "John", - "last": "Doe", - "grade": 8, - "exams": [{ - "subject": "Maths", - "test": "term1", - "marks": 90 - }, - { - "subject": "Biology", - "test": "term1", - "marks": 86 - } - ] -}' ----- -==== - -V2 API User-Managed / Standalone Solr:: -+ -==== -[source,bash] ----- -curl 'http://localhost:8983/api/cores/techproducts/update/json?useParams=my_params' -H 'Content-type:application/json' -d '{ +curl 'http://localhost:8983/solr/techproducts/update/json/docs'\ +'?useParams=my_params'\ + -H 'Content-Type: application/json' \ + -d '{ "first": "John", "last": "Doe", "grade": 8, @@ -440,12 +349,15 @@ curl 'http://localhost:8983/api/cores/techproducts/update/json?useParams=my_para ---- ==== -V2 API SolrCloud:: +V2 API:: + ==== [source,bash] ---- -curl 'http://localhost:8983/api/collections/techproducts/update/json?useParams=my_params' -H 'Content-type:application/json' -d '{ +curl 'http://localhost:8983/api/collections/techproducts/update/json'\ +'?useParams=my_params'\ + -H 'Content-Type: application/json' \ + -d '{ "first": "John", "last": "Doe", "grade": 8, @@ -494,7 +406,8 @@ V1 API:: curl 'http://localhost:8983/solr/techproducts/update/json/docs'\ '?split=/exams'\ '&f=/**'\ - -H 'Content-type:application/json' -d ' + -H 'Content-Type: application/json' \ + -d ' { "first": "John", "last": "Doe", @@ -513,34 +426,7 @@ curl 'http://localhost:8983/solr/techproducts/update/json/docs'\ ---- ==== -V2 API User-Managed / Standalone Solr:: -+ -==== -[source,bash] ----- -curl 'http://localhost:8983/api/cores/techproducts/update/json'\ -'?split=/exams'\ -'&f=/**'\ - -H 'Content-type:application/json' -d ' -{ - "first": "John", - "last": "Doe", - "grade": 8, - "exams": [ - { - "subject": "Maths", - "test" : "term1", - "marks" : 90}, - { - "subject": "Biology", - "test" : "term1", - "marks" : 86} - ] -}' ----- -==== - -V2 API SolrCloud:: +V2 API:: + ==== [source,bash] @@ -548,7 +434,8 @@ V2 API SolrCloud:: curl 'http://localhost:8983/api/collections/techproducts/update/json'\ '?split=/exams'\ '&f=/**'\ - -H 'Content-type:application/json' -d ' + -H 'Content-Type: application/json' \ + -d ' { "first": "John", "last": "Doe", @@ -583,34 +470,8 @@ V1 API:: curl 'http://localhost:8983/solr/techproducts/update/json/docs'\ '?split=/'\ '&f=txt:/**'\ - -H 'Content-type:application/json' -d ' -{ - "first": "John", - "last": "Doe", - "grade": 8, - "exams": [ - { - "subject": "Maths", - "test" : "term1", - "marks" : 90}, - { - "subject": "Biology", - "test" : "term1", - "marks" : 86} - ] -}' ----- -==== - -V2 API User-Managed / Standalone Solr:: -+ -==== -[source,bash] ----- -curl 'http://localhost:8983/api/cores/techproducts/update/json'\ -'?split=/'\ -'&f=txt:/**'\ - -H 'Content-type:application/json' -d ' + -H 'Content-Type: application/json' \ + -d ' { "first": "John", "last": "Doe", @@ -629,7 +490,7 @@ curl 'http://localhost:8983/api/cores/techproducts/update/json'\ ---- ==== -V2 API SolrCloud:: +V2 API:: + ==== [source,bash] @@ -637,7 +498,8 @@ V2 API SolrCloud:: curl 'http://localhost:8983/api/collections/techproducts/update/json'\ '?split=/'\ '&f=txt:/**'\ - -H 'Content-type:application/json' -d ' + -H 'Content-Type: application/json' \ + -d ' { "first": "John", "last": "Doe", @@ -670,33 +532,10 @@ V1 API:: ==== [source,bash] ---- -curl 'http://localhost:8983/solr/techproducts/update/json/docs?split=/exams'\ - -H 'Content-type:application/json' -d ' -{ - "first": "John", - "last": "Doe", - "grade": 8, - "exams": [ - { - "subject": "Maths", - "test" : "term1", - "marks" : 90}, - { - "subject": "Biology", - "test" : "term1", - "marks" : 86} - ] -}' ----- -==== - -V2 API User-Managed / Standalone Solr:: -+ -==== -[source,bash] ----- -curl 'http://localhost:8983/api/cores/techproducts/update/json?split=/exams'\ - -H 'Content-type:application/json' -d ' +curl 'http://localhost:8983/solr/techproducts/update/json/docs'\ +'?split=/exams'\ + -H 'Content-Type: application/json' \ + -d ' { "first": "John", "last": "Doe", @@ -715,13 +554,15 @@ curl 'http://localhost:8983/api/cores/techproducts/update/json?split=/exams'\ ---- ==== -V2 API SolrCloud:: +V2 API:: + ==== [source,bash] ---- -curl 'http://localhost:8983/api/collections/techproducts/update/json?split=/exams'\ - -H 'Content-type:application/json' -d ' +curl 'http://localhost:8983/api/collections/techproducts/update/json'\ +'?split=/exams'\ + -H 'Content-Type: application/json' \ + -d ' { "first": "John", "last": "Doe", @@ -774,29 +615,22 @@ V1 API:: ==== [source,bash] ---- -curl 'http://localhost:8983/solr/techproducts/update/json/docs' -H 'Content-type:application/json' -d ' -{ "first":"Steve", "last":"Jobs", "grade":1, "subject":"Social Science", "test":"term1", "marks":90} -{ "first":"Steve", "last":"Woz", "grade":1, "subject":"Political Science", "test":"term1", "marks":86}' ----- -==== - -V2 API User-Managed / Standalone Solr:: -+ -==== -[source,bash] ----- -curl 'http://localhost:8983/api/collections/techproducts/update/json' -H 'Content-type:application/json' -d ' +curl 'http://localhost:8983/solr/techproducts/update/json/docs' \ + -H 'Content-Type: application/json' \ + -d ' { "first":"Steve", "last":"Jobs", "grade":1, "subject":"Social Science", "test":"term1", "marks":90} { "first":"Steve", "last":"Woz", "grade":1, "subject":"Political Science", "test":"term1", "marks":86}' ---- ==== -V2 API SolrCloud:: +V2 API:: + ==== [source,bash] ---- -curl 'http://localhost:8983/api/collections/techproducts/update/json' -H 'Content-type:application/json' -d ' +curl 'http://localhost:8983/api/collections/techproducts/update/json' \ + -H 'Content-Type: application/json' \ + -d ' { "first":"Steve", "last":"Jobs", "grade":1, "subject":"Social Science", "test":"term1", "marks":90} { "first":"Steve", "last":"Woz", "grade":1, "subject":"Political Science", "test":"term1", "marks":86}' ---- @@ -812,29 +646,22 @@ V1 API:: ==== [source,bash] ---- -curl 'http://localhost:8983/solr/techproducts/update/json/docs' -H 'Content-type:application/json' -d '[ +curl 'http://localhost:8983/solr/techproducts/update/json/docs' \ + -H 'Content-Type: application/json' \ + -d '[ {"first":"Steve", "last":"Jobs", "grade":1, "subject":"Computer Science", "test":"term1", "marks":90}, {"first":"Steve", "last":"Woz", "grade":1, "subject":"Calculus", "test":"term1", "marks":86}]' ---- ==== -V2 API User-Managed / Standalone Solr:: +V2 API:: + ==== [source,bash] ---- -curl 'http://localhost:8983/api/cores/techproducts/update/json' -H 'Content-type:application/json' -d '[ -{"first":"Steve", "last":"Jobs", "grade":1, "subject":"Computer Science", "test":"term1", "marks":90}, -{"first":"Steve", "last":"Woz", "grade":1, "subject":"Calculus", "test":"term1", "marks":86}]' ----- -==== - -V2 API SolrCloud:: -+ -==== -[source,bash] ----- -curl 'http://localhost:8983/api/collections/techproducts/update/json' -H 'Content-type:application/json' -d '[ +curl 'http://localhost:8983/api/collections/techproducts/update/json' \ + -H 'Content-Type: application/json' \ + -d '[ {"first":"Steve", "last":"Jobs", "grade":1, "subject":"Computer Science", "test":"term1", "marks":90}, {"first":"Steve", "last":"Woz", "grade":1, "subject":"Calculus", "test":"term1", "marks":86}]' ---- @@ -854,7 +681,8 @@ Set the configuration as given in the Setting JSON Defaults section. == Setting JSON Defaults -It is possible to send any JSON to the `/update/json/docs` endpoint and the default configuration of the component is as follows: +It is possible to send any JSON to the V1 `/update/json/docs` endpoint or the V2 `/update/json` endpoint. +Both use the document loader configuration shown below: [source,xml] ---- @@ -884,22 +712,9 @@ V1 API:: ==== [source,bash] ---- - curl http://localhost:8983/solr/techproducts/config/params -H 'Content-type:application/json' -d '{ -"set": { - "full_txt": { - "srcField": "_src_", - "mapUniqueKeyOnly" : true, - "df": "text" -}}}' ----- -==== - -V2 API User-Managed / Standalone Solr:: -+ -==== -[source,bash] ----- - curl http://localhost:8983/api/cores/techproducts/config/params -H 'Content-type:application/json' -d '{ +curl http://localhost:8983/solr/techproducts/config/params \ + -H 'Content-Type: application/json' \ + -d '{ "set": { "full_txt": { "srcField": "_src_", @@ -909,12 +724,14 @@ V2 API User-Managed / Standalone Solr:: ---- ==== -V2 API SolrCloud:: +V2 API:: + ==== [source,bash] ---- - curl http://localhost:8983/api/collections/techproducts/config/params -H 'Content-type:application/json' -d '{ +curl http://localhost:8983/api/collections/techproducts/config/params \ + -H 'Content-Type: application/json' \ + -d '{ "set": { "full_txt": { "srcField": "_src_", From 3a96567ef2d10a046e9ed03fea6852a2ca57ebc7 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 19 Sep 2026 17:59:37 -0400 Subject: [PATCH 36/40] Updated screenshot for v2 default api. --- .../documents-screen/documents_add_screen.png | Bin 181627 -> 117795 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/solr/solr-ref-guide/modules/indexing-guide/images/documents-screen/documents_add_screen.png b/solr/solr-ref-guide/modules/indexing-guide/images/documents-screen/documents_add_screen.png index bbb5eb7c4b3356be64d78816bc44282a1609f80e..2c805618ebe9e2ac65d9a5f16382b964951d7be9 100644 GIT binary patch literal 117795 zcmaI82Ut_f);=Cu5+FbTfl!mskq%1l9YLA}5a}Qwy@y^xJ+x2+6zM2>M7l`tQWXNC zAiYXJX(9CDAJ6^1@7~|N_x$rbdG_qt^Uhi`Yt1`r&rFhNWS~t&!9oE50H}0zG)w>h zva2L<7#MhUdHieP5dc8s@1m}5~bH~fp-ciHF*UQZ|$Vd_Z;7|9*wCVL2 zUHf48#sn*uK+Vo3k*Gc~P7&bkEO<>*>@L}xJI}(&-9xzrOuXq$1&-lnUFKfZ33t7` zy`UN(+PUX)0*%h7`JB7cob&x_7c$R%n;v$u8ZiqV67z3lr|b8Yy_;xW4ME2j-1+lS z?W+bgZRsSu!K#~>M&BRd zG;hxa-Q9N>a^1Z~M5m|KNM!PhGr!DCqex*a0FO=mn|HN0ix0p1be@rm!7g;9bRASQvwt#^Q`!&i17wbxZQf=~g7JV;h{ zuI}9&&2%5#z74p3l?DSyiCF$J8qw9A5)teFrL~Co0VG#>L;zv{Al?N)`d1nNFu1z@ z{$Q@YzcK%~5*HEwZwWF$5y}6iiNk&ujcWIKdUYZ9(lPf102q0Ge~ENWxOcBy#mnWc znV;EheK~ticM)3$Pdi5ul)Kk&6hHwbca?N^^s_~v+}%8UW$(d+q{SW-=PLb2u&(BLvR5UO!P$W=N z#M8$~R9se8R#Z$vR6;`ds)Vp_kcXcwO4!4f>mQZ;Z#^20zV<#YUVbi~9*E!d+S+;g z`zdmA{ucCqpMU7-h;sR-BoE*JY1UN(MSnjL6&Dc`{ckWomq&jC`~Bn}u)o^%4{-{= zE0Z&FK{>jaYq+>OdiY+crX(&VA))Y>od0O{yXBIfO{S;eo9xZ`b)^aW&aOKLG-sx{>4WB zaDcyZuRK7BLP7NZyh({dKo{cz0H^?THB|4Sh<@giMc&gq>$7f%e9=;Nr-p=qQod_s zv~#<%BX(-)?eVwJ2D`}osi?R71POoT*`#zaD)|8`UKfYWH0Hj?a1t;6-K|9JYZxNHREzu^B_ z;#E610Z`HI|0aX{Z)E*08w9uo`v0j$#a~N>Bl1gAz&)w|uJ2VHQ7mbZ;7*n*V&Z=a z|Bq(LaUqDSi0g{{kAV&_apKR_>_){;qRWz;~TUH30Y5r|*6Hd!-*M5OV@t zTPeVRzgKxP`AX%@hb`Ch{zSrw!D+V?=ispJY>E)Qs;~|0r zpAMetJf6ZnU=OXhIb$DTA#VRC&)3`URv!spS3)yj)U>qXQkW(C@ca9MhyYE2zv=BL zyyjKoPL|Cj?F9LExvi!_TyBFq%0bJbd|^@MZy)qUobUYFTX;kkbdsI>?D0E`cjDa_ zz6U`Z<)?~LHGN@c=A~Pw4@}Tn_ki*V9UnjH{}?Nc=iwemY#6%xT;lqFlUfwVdF29e z_s=^70{R*|+|J)InSE`QReJV7S2r7D($dRMiLZV8K?!7WF>f*be6sNUM*;e`_wAy8 zS?!*Hvd2r_-TdY#>e{|ebbmBG`kpG{a=PASMP~Ggkkh?^dOZJslc@rp&>>xIzsvO| zY>TqZ?R=r(I0t@?mUZzJo(ZGN$YkHkv3qxGKYDn0zaYXrP4OFEB73@(R+Kfy_AU33 zo4Ym(W3Gjo!px(NK&*$D?{9FFPuKDSw7jPeG=fGgG% zD14d9X9)c}?>`RGSM-?YJn;Ga-CONP#@~+?b0`L6V{Y_klr3Z0<#eA__)FpQWv-o# zoaq-WJklPU>ANAncr;p%Z*`S_5-)EgQDT{19$PT$n-b4n%>GrWy9FE;F$+vG`ZHBihHbg=i9Xo@h0(%8LI+SOB-HNCL2Q#IjH6<=j=Uyt^ z=*4FX3pc-j|2;?lW!i5wf6^kEn(qYh%?3PK6SiEYIVr-Dk)7Nxzr(ciB&RTMg&OeG z^Zs-fJQb%`k@wg=|HxV`|E>jLj;Vo=R(P=lcO|ilnsKW3=ok6A(d`OR^WIBasQ9!( zA^6`N`HB-4uNusMlV8myQGP9OoE<&O5t=ls$}X6*87xs%z_r|b>P5C~1U!=sR&t|@0fe^OHp9?nOc2;R?{I~#p*cM;q%;n7wxxuyQrqufJkDf?I5p7g`- zIV4PuX3BrhQeP8P28Bjg;P)aoV^p#&zRDjg$OPVMwIVNu{{!A&pxaz2E| zAZYP^2WeAgqTgbdcMzMeCvK1vGeX^ zr*->~^WmD}zz$idQaxGfwXDCN0V|k^Xd_+wUY*8!@oj&daq!i9Wm&#kHm{Q?3n;TX zy8TC`!>4@5lb}?#9daE{ssyEw8Vw&8G#1Lg5% z%aC{;X_rTJR-xmMI^-&L5gE9_uJZtn1W9XDckTLUN?*ys|JxZwTAY#W1%-vyT*H-W zltjOoNafp=bDBv#|1*qVr~SbQz1Phsz|XIBIEfIzvwcF+6XJD-DX-7MZ%+z9#}PKTM*u&`MBM{ci(79RVjTk`)*Xc`^Rs*>Wp6hArr=_OKWXv~m%zgYhQ79X_iKoy z^#XI2dBsGTe~k52M_jkrp!ypE^Xr#(1NQth~JlJO!;VZ2a>%~vmi;;)YQ&yY?9 ztO4bJnx9BIKDwFM*=-CrV+=W!wLS4{t5i&4uQXoFKJ_{E^x0=r(zp69F>G1u{Enwh zXu-YpLS(z{4NcBgD=AaxVfzd(R(MiabxIUU(?RWI{dmi;t+T($X5vh0ZAspeW0r0u zX8c~=)qLsr>A6Qz`>)!VYkuZ`$DL{b(pygEF~;vS5evffI%mi3fv@^2x+Ubgh2Qn_ z#;(o`nW&HRynlShPesQ=D{f7OxH{LgaDP$HuK?|ys`vs&xhD(&8v8gSr>0l9$)D7wSDt34gYgVf z4b1acE45ABoqcb;uNSS}I_7yu;qcl!N6@3;J_x?$DSWZdl;_v)ueALj(PN=Qf})&B z^m0SqzC$(ff7m8!C{87N=CS?P3UjHz?T7Efwl~k#?>C&^?>iT=X!vL^6VX0{HskzP zcCH=)_XdtVSjQ1;?y1@gdMZD-3TU!38#cM;rr*w(`>Z_5{-L9gy~8UP{Wg4bUQ=_( zqG3Xq$m-$hw|*1L0vF%1{bdUQm%>)}r759g$Hv6b6*dQW0{Dbun+;;;1TCF!Vl^P4rhH0XPiq@h7>!P zN8SYPEq};<_Uv&AtF(y!x9}jo%geK!nV;Y4+=qqV*S~GJ{#=EB=f+pwcJGzGZ|@hv zU9YOB4m>?wWBvx?X`O={TA;%=G<~D0|W%7 zew0xTd zfnF{%&K2SrI*~#9-%n0g=+-o`Aqe)n8=sh5U+6zr28LNToS4tz&lx5$oz#8C7y_kUhSP8EHz?XZbLBwSFF+vvB_yMl8shy5r9+Q@V`R`mOD`zv;?iwU) zKY#vQA_$cCv!g1e;C2;o@G4@t== zv>V2{b99y0rZ(--Q1|*(6|>}{I3TPtpsm zW&M7Z|KO-U!B>RzMo06Blhl6QeVAS(67R!Q#R~+D>_I6EG``Vl}e*b;;-Vr<-^W4NQ1%C3)JbSt=$G>H>fm2O< zcuP@cFZ+$#$#KNj#B=oVO6Ejk;+HM#3$x%SCp6h&ZF_yJ;VRfy#$2A&%UkFp%D8n# z3pZO+t02c02n{LHR7h_4=>}h(m~C`FU^MQT5GqS--^#dv2i5eVtnAoOheWifFyivt z)vJ=d{7$Pc?fIRT`g@biF*{cyOLKDaK-+vMPAPQ%lXN&HsoW+AN8*l&5@=lB&^}4W z6jH5iV6wBnlQSi>D}|j-tqx{hEFQM(v`gYcznVL_i-|(SUOOxHt|XsQ2bUUT zD(~oitwmAvC2`H)RFF1QJhNQRqz{{^WtGIO&{iTiWc*p?jIqES;tk>@vsAB`p1ON zXC|}C5c0`eIzAkclvEO;LLF~sYLbLGwjQnKms?+Ou*!>e-YW<)-(SB*7wNIM80Yf! zG+{;fwQ=#-@O=2bR_k(sZwOmcmmPGV}Qtx0?HkL@}JbWJp@}Ml~%`(Tpht zl}A2>yESo^Zoxv94{9BBYoeql`IR|T(LgFZ$Mq~^7P>W^52yq+cH9G#k1i&vqY@@x z^?py{snNN?cG8P;401NliObN&-Y%+)78CyQTcpKSuO^+FQWX!)}hvDwn?O zM*KR8nE#wcM5Up}uZ^uU>KC#l`GJ}b%*v5UEXcWQHJ@!KC&3(p0&j_ZQvew=Q#nd8tklUm3mcB@drqk3b9h(-L@DIP?X$wVrLaw2$J}Tc!4j zURJ7w^$lwiSjvb_eP_M<8A5=HA6(OvNbA)z1@(kY6K`OwHz8?k)rk}Zr8sVwzfZ4y<7h}64ZR@Bw{H{S zZ3GH+h8Kq7b#F|62}i5m5Z+a#vE#8(pYOW9N}k_e2d+}`OA9r5e|~!&VSl0p>XBcL zp*Kj#1G<#N+o(UcQI}I45IlhT(VxFG$lv6X$z4sLI7sQgX^T}?*w^Ef#u_GY;teh` ztPA*PvSJ$y^6&6Y(;j1`hz}hgqaGlra)x+k9I zNNzABz5emDYKOCacm%;e{E7-Klqai7e$+q3Mu-~|c^l5mp;l5Z;AWyu58$YF}oN!5-NjZRxB%78*%^bZbb^9ev0?uRP`?z&GkEYW0hi>djy8TT%R1 zlTWGh1ZLr>Wcf*^uPYC)nZc>ru44bM6aX!(vj__qixTFhv9Gj*i~D9kwz42I}FI`5O2Ji zgF6x3jj_*Vj~CHNR;MtvpD7w<$yCez{Lv1*ui95L^jep~ zyU3W1%}pUe+22*_^CK=#0v4k{!RpJ&k|VpL1%BhY;n0CFyyoRD5dHjY9lriQ~dcW1j>Pe71zmijK-O$%bT1sXAN8294_ZHpZ(~!;A<>y5(5~|v`+5*&aVz?L- z9}60bBq9k7*V$M8b&^KYV-xqn2i6Nqf^?eI4%JqUL$si_ z7EXm>4dQ5;%0=L#_547q^zv)HbipL4SHVcrsALd`&rd*z-CkNUV4>I;WOS}FzvbE1x` zZF@qkU_a@ZhW&JNf>Rl~iTgD?K`+^QHr)Mf-G@t4kCLJm~v6@63k2Ce7A>#WRrbVZHizxo<>s2Je$Lp&Z9PH15yiAt<_UJ_yLzoz%9%^|#D7>6vr#`?)Uzz5 zBt$@zbmQBW%wGP{KJLj8j4Pi1oO0=Xo7f!R1%e;1B?=isu0n=`B!l8NQQ#8y;`b9I z8~X~R5-_Y`5nsKNAH2AVgv)ECMozJ3ob%7wVO0w&aV0ESwlt%A?^6UlGD-3o zTOVV>j$#Wx_kUg>O%95wE!tnrza*V~4f2zMRLIMkkSiuo$aQ7hR_ND_?^4hZE3n1U z9%zQYrpZpcyf|9WYWaSl(*p4yH8JTP3r~nw0(B>zkH_~>%$H`RD=E(M(JXeJr8h)A z^c&N>Y>QsM7KT{k94-662t)$e=l8AaFui>)lWN@aly+Gb-Az?RBIHCol6nC_dmX1b00A3U+Zc7SKr19T`8$ zlh**mZ1&n$?&Gif^1_yY0wIJ;#m=pVVG>No)6pmm-WKfcZc(A1!_g zaY@GR*ba|XRvV6K`kfVoZ0{|N(N0V66)_POVr*&b-JsLg)n?Y)lBm5>4im$4m2X*V z<@NVuhTxDE>)3{|HogK>_t6}|pYXPx(#>-EhvdlpM|Pky7;B7AFGWzfC^bF%Dt-P^ zSW@NKciqfOBBX+&RtMH%SH-yl$A21&EidZ9e8d=#Hjc>$S+0 zsBV>$+e3#X6wpbJ;=_4Iv*P8t3`#u#G}SLH*?k6(8p3~V=vB~0-TOJhg1)ZeuZs94 zynp-Ek3&1}^0P81MOe7&PH;E6kCB#&kQY>FDuuP@)Y&GeS zj2DtU76fEdKpQbFQfQ%g(&0cjY;`Z@T#{eg2~p9>MkHFWl&A24f`k@$h@)(_qVw+c zg*rnyqA=(Q*xCRBC=7u@$6ti`vMUz5ErOeFKaH*~XO`;@1J2r{8T5Z;a3DI>U{^Uu z^SP*utNBGTxhEb*x0m!!&=09k0Jks z2#)Vm){_Akmg}F-!e5%8a{fa0(63bA5ZpYe^uWXV zw~KSSu1i%&QEoU2@;YtPuTZI})sxT9bP4Qr;2B3=6bBYk|IOj!+LR;lnW;kc%f9kVSR& z8IZsUOvGu;DhnI*W0Y<@*+2IUR7Qi8IdSydthO8WE;IE(G3&TrV>?)n*L;LR!^SuR z<0>4LJB{J((;navlMdU)4K$Q;!hZ>`ut-1^H1>$;8lq-R@)j9 z1Zz!_vo&~&l9Tr018`*yT7dvGnzS=0ric>Lhk@l|&=EO`lwY=l-%DNq~iG0r%n7F151P^)`woPxB z&Ck9_np@&{ll^SfQ6$io$vnK$;&3&4UTXSLKc|-jpWOKJkF*P`@XNK_99Lm#nF+aq zh$lQR8R>4Ic5kz80=40(58cx`OA@-%h_xTcq7}x1z*6(UA+9yQiocfaY!`TO!+jTm zzkP>9j7XH<_F;P|U(tI3GB*1X+eHGTkE4#0S%3+^v_3*4zkf-8B3_IbB#(ysfQd6y0dX8LX0TV#)F|RTNd$KX zWOJ{q2ihM3H6%`E8)6*i@`Q;(l6N_K8y&DIHbwMIXxgE!1+W8&7X4b+6(yAj!CYUE z$I<5kZN++3WMTEN6r^<)JDLi8jVm>*S_}_^swG6rmqCHTB*myQstp@%lL)93R38?o zBW82LKK}4d7&N;}fRvMj6L<&WFu+tdiIRwGbPicfY%QTjp(0rw#7Oi8xHqr$z)0xT z3kU+z-jO_31(0@xIqZh48KL1LVt2 z8Xsy;k>o pMdkaRsCoAvXpZxU0|#1ukTzuZ3rkhdCPkA)ex`Z;lMGi|s;gNH)eb zB1cyR!D}kzgEO5`Bw?)R#TW&E_s5+$?S5jzyuOfPm8vXSyu`g zdI52*8f2D8%LE2Id@y$I+SyaDSJ6ea9+njyd_?toi&aS=NmcBXE-35YY6P3G7~+k3Qx>yc0y=<>&(A# zLajDi9|*+bWSe8&L(#g^ur(WUb7k$*bf5!34xYl*%W!u4tc*Rg5T}$AL&B;6PE;lo z<0LL{!dNTJ}^%hDB; zr1%tBCN|)eq4!R_TzOSLNwRC1UU;_#!vO^&j+Df9 zGfn|Co*|*4b|6EquL9wS#iZV1Xu)VZJ-MzR zj=oO3u?I20BwoS@PS{7Xg^{~_E?-X6jOee^S2BJNO&{=ey)vSFPgo9 z-dlaZ&d{j5!Y5%;`?Kkul}b5U+v&!_%{A$RcoE}K99OWx+`h+B*vDSrA^&6-twIop z6B?y|9vjGTLF%4<1U-l{*c0OBIMkk?d>*{Q-pQxa>WTx%89uDPL-?2`e_u&MQPNwb zkjW3(%hWEww8Ja4HsNkh4c5vfPlR*NtVzhvFN2y~3tJm3GGl$}@e zkv3$on(gZ;cBSItsYmBZGKr|j$7EY|_w4~wA>?4Jfa>{W_;&0qmfLJyQ=5yWvnKY^ zbvTI3_KS%9r=Nl^$pfSJJm*8i*`zYo2b2>A{}h49kQB$JGXR5N957gyO5cFpZlrfo zdM5yaPWYyx@IAU$(x;~_qn#O|8ZPeDJ+L(DR^ zS>k5MLTDtIn}StYazI5a9PuO?V}*HT9KSTO_(#;IOhcs&-RlW#z<6U~J_XkHu1F#Z zzBdw6=q$i!q%SyLT(cdPOh#ln6kkL3I$F$`*cCF65p%`M;1W!#UFVTO{e__ zgv3;?cyK&QkfgqdDO4S0*Lk(T)>o9M%G<#_6@Bm83d#jI+Y<<;Ka-w(9Wp?feB$(c z9gH4b6z}mT04lJKetaM8GU3U~DDODJaq9P*ER5Z;As9~EU7~M*8kIa770V2bmBc_SEnau>jN9g&fmKpGHy_$3qD=`Y?Px7pU|izxaWs7s?I1}2`3S(e+ph83{ObAb6oW8Uu&fu(y&4W z+*X5Y2G`ItPB6(BTUEcXUw0OJF+=>(t#)7^x6nDh5 zst`oNM8LF4Y!KP%wu!k!ZpSNtY7;x`YlzCHnkmitt&V7$BRW|ud$^LxuP-lNfS39d zhekpmM0%TvN7t-mksbv4xn+(Eprf6b!A$4AkkH0qdc`^DqNs9XvNR~kbiHPcSbdyZ z$bo#77lx-^mV=o9HyI#g23LMbZuI3X&tBCV*5|yHuP{f88|1dE^C43T zHK+8|e5q#7H|ho`cf7q`T4st4fd=UW37=Z;^f2<&9J!7!Oo&^Z>o~+2OFEz`b zHul6lWTlZCQjcId{j$5A^iuO%O-ioLA3VRnAajFn$hYds8myc`GJ7eC6oUylTo;gF z)>g`=X3-BFYNG;`Y;lryGbT3-PlbQ)FDGzTKN`Nl@}Ogql~_>1^f>B9RlnRwwJWw7 ztJu~m(J8TbKmMYhcR_JVCjI?I7hU=}X*dq5>Na^iXrL%q1flvMvzLeN4Cf>(!bdlJex~S(J z^mzPz;r_Aw$4yU@T==%r)xPMb@B?$bUz_xSHim)XB3y-{Aq)+ zceWjnz&WZg04-qKs$#X^9>wlm)yBTIac&qGHnJ4xhT7KryrSg2uWDD=gIlMsv@hH3 zitHLd8g^k<O~@Cu%Di=&c&H0yCC=ygKj(ECIr( zmtppkxN`Mrt^F(2f?aY4t=2|F%I=hnS4=s`iVUTy@=>Kkizln5{uB`k?)TiKjELWX z^4hRoewTkx>5STb!W;AAXU(Wa6>hkg=IG zkvXQ({B%E=YfNl+FN^AIHCC)8yLL9^ zRO0w@0U-n*B7PmEQmwH9kS4tlxXll;Ep@pfC#7Nk?kZg@74LQDa_C*-sOMcPU{1g| z-Hzo@)DiE(SeM{NY{Q2gOx2XcTLHr|vSLzM+8>`(gC~%5VoBd~7NC-=!hgPBWDuhq zrI%V2Ka`FDkvk$6#&&15M+fd;{l~QsFmgDredEXi&)PTT)k|}FDOU=nAp<$pa`pKH z`3C`)shfTYd7eXq8ssrd9I4Q9$TE_6qoX9`mYRp!yt-EG1xcy*UQ4~tSbHa?LCuOJ zjzFT&zX)finA98c)yeeH*8SjqhS)ln1mW{qXG=yc5xk{ss7&f2Z}ACx#UWv#JH~TVz<_rp8fn; z4PBY61Xt)=6kz%G?VvH+2J@n{5A!@7Q@NP8rO!=8n^WUo{&lLwh>`R<6;Y#5&!@_k zu4rj)ADP!)Yc8)n77I6lT>hK)!=lTD80flNkG4SpN0vtx9s!kGbqiKfxVSfZH>bOO zUH9ig3ojcMKL-l?o=(`Meen+YrCP7aBkx}D#bd1R0X!g-Q2YDDQSJJtnzG=7w-?Mz z7C(BD77CBHe@I;ng1*UN+fl}5LQzMr(wc5Nu1S#ew-y5F+(r(?g+o?}Eu zY^f4KFN6NM$ThBC_rH^l*WMqx9$jGu6x@RwujHyC>fXXiAZlXGVjQ{NPGS-tg2+gb zkR|5O<=g0`$gaqK5-S~JHKYQ1FWQzpf&LMdIBuEQOo1NqV>eWHQta|+G2iGaZ3f!0 zttPtW8n@NevA>1YCnypkrfu7Sx(cctAsxj-4zyVhTC5xt%2L*-taQ%;r>6C13As?31q?_?yr(qg0xLFeHl568ea*L(54wr&(j^ph)ZpQ} z;O>usVoKtq>iIs(4GNbjCw;yomM@g{ znxhq7RG>f_D``sf;l70u##VUxnC3;fy4`v*d}mneyfY<6z?H6mvM=z4&|Y6px?EA4 zZKOJPor1T!)Lb74CRFnCXe~mlb43v16Lchw=z0Ui)6gWN(0k5SCsZe0F+^Jc6H3FB zajz891}Gm13trvaydbfkJ%%}xey@7DN#*Wto|LwwxR>py^eH!9Z2>U9`lPYvVA197 zgMwRs(u`B8vGiKA)X;XT4Xi|U70cls-No_|655{XzC&b$63r_ZHA`u0J-r9|mzYo9_X&67d+Fgi#SNu)l1vP$+GRgg&BI+vK)5%eoQ zU*WQ)esu8HBOzG|E*WJ{UG zPn3|u>iwCow_)G;`OPKiMzV(jq6LnqkK*FpG1M}5tW~Rsw|ARn+lZ3ZwzixT;R^N>GR`pGnJU9EUGqO=ju*#`0zOw~Vh07q_ai6dI z+0w5ntuX2!5tP8-7q1j`NfnCoO<*;7swB}cTCRvuI$9y$ajCXdlbKf5?imMFyTRqr ziV5c4Dosab8*R7b7xPsYNKX=T1m}~>1pl$@Oddt01;BHjTuds@4lkK%KhUptx%2Xy z-K2}t+W|cAPV_$FjYT=;*mV{1r0+IsOV=IwA9WW2Bm4_}EH_0w8mnFx-P;O}I4VZduiPF+qrGZP?BU+%*FX+5(I<`s%=AInRFt?`}*Y<@8}=Uiwa zN<2|8&z>gFHs}3$W{+=_vvkBxLVRTt)bEqR?76E=ZS3amowYJh8j=6%?Ceai;R=O- zvR~No+Z);AVifQ3vkpff0OCeS12H)SIV`L0ZtS}FCUdST&bHI$uFbLCuDya7Y(*Bx zjxmA&a`(7XQGFxRDt$Ka(gbt!@qTa@wnsjiNVbnaEp|HZmMp*mvRKur)YIkc0CB4Qi_t{JV$RIxci^z*v9(ss^tb0kl?rJ{mh8K> z05pB`-2yrt+SZBPKRS(GUh>PEMT7bz^gP9^UWX*ye^lBPyCxt6LOVpUMICegf~}$I zZT(6kvUucVl1+iT6eOY9KdXgk=1M$ro61GZM26=hXX7I|3%hhunfUg|*gNYe*%AY9 ztFe!d>{67ntL~~j9v2Z14cV^Y7Fc9rg$GkTveCBaicAX*9Ru0m(Bv+vDn%Vv&MdpC z(b`BpYNcaC(ez>dwvdH}G*&&E-x%adHy#BoNyhO1+#!3d!v_7fioCCb?z&Mxe}&;8 zk-F4!LSW4rZiUzb2Cc#&jWC$Fk!>g5ldLwfwKzvewLHAr8fD=?#UK zE9A`vztW_|L&&3rz15l}`O7Nm$+KxCzhSGam3M3@21zanwyKCwxt6ubR#wCxE=gHW z1O8CroM2r-i5g2Szq~z`mgz~zzmS#m=+e-IC453nqZd{O%92V*I zCS8t8V+*E4)7OJQ(NsHfo6(Kr+E4dFi#^Nd=tSq^p+q>v9ktd&FsQwT)TR@8%03^3 z!B0?~qOCIdHiZda6iAv>ELCn~gzI*nWv{>5??QvlE@S1s=TjBvi>M~6=os%TsxEw3 zrJi3QXrszaMCQ@;0-IE9ogm1h7C)1FW*~=ubT}P6=z3Do=c~O!NR#>rQu{Ti6|s>s zAQ=WYNbaY*V85Vs0TnC9vlWNGve=}ER?PM*b;lR1w0%;FCg|zwMAGkIpnRla2ii>t z^=eA2VYc3Q0J)xGjNFG*l}<76R`!vYV@X8ihFTala4ZcQ{!+##C3w|RL(ymw$JL1D zvweEZC3LDXquP(+MHzg~###<&Oe+w3_toi_h`Zcwoe*r+wcr(*XHb<^lYenBCcSHb zPIx)G7RJ2%K}2N|*|%#08P)vpBpj$%MpZOfrpiGZuKxIr$55AiL7;e;$c|QwOpUBF zC80^5aV!l#ud%a{>C8kJoHE3Gve7y#9(8n)-$53XS4Q!A_ znD0}yF~ycXY4t9Fi-^AnF^eqfi|}c!!n;&kA0Hog0!``G3XQTa`I{{4*93HO#wY&t6O6F>h#}k4)JH8uST0XL@ zVqP{pPwA_{wT!ys6{Sx}w9a35i%=&h9|GL!9pSF+3teCRCfjC_$Tw1pqZZjq3Umu~ z=Az2(g3nDkN0$yv|7=8MvdZ-oyDV|YzKDBED$V@S=4v|yA@faN0l zLG#jX;8bKXQF2soWZ{J#?3yiVDW$6uTpHCSH<|&|V$+f(aR=ft5Qs!V4iOkVWOD;r z(286oC!cgKJWsXi5#wZYi6vrX{U!;%Iw;1$!3RUdbOAWSAx{xCll(MgJZqiD>pc|` zS}fj@iyw;kIF=tR{;|YiD0UxN4tpi`%FZU~J((6GiJ$@)9jM~gob~7uFk3XgCw~tUyiQ|76u*# zdA4{JO2o(!hZLj)rmU;WjB`Q`pfOQ=z3>%UvYs^n~7aaCytS5j8v}4 zJYuXh)^+R@JPDi4ROU58Y`QSv92JU{i)l!QQLD