This is an automated email from the ASF dual-hosted git repository.
gerlowskija pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_9x by this push:
new 50a7ccf5a4d SOLR-12224: Add APIs to read collection properties (#4071)
50a7ccf5a4d is described below
commit 50a7ccf5a4d947b7e885b9865f615e587f70961c
Author: Jason Gerlowski <[email protected]>
AuthorDate: Mon Jan 26 11:25:27 2026 -0500
SOLR-12224: Add APIs to read collection properties (#4071)
Prior to this PR Solr allowed users to write collection properties but never
read them. This commit adds two new APIs to serve this need: the first for
listing all properties (`GET /api/collections/someCollName/properties`) and
the
second for fetching a single property by name (`GET
/api/collections/someCollName/properties/somePropName`).
Corresponding SolrJ "SolrRequest" and "SolrResponse" classes are also
generated based on the OAS definition for these new APIs.
---
.../SOLR-12224-add-collprop-read-apis.yml | 8 +++
.../client/api/endpoint/CollectionPropertyApi.java | 23 ++++++++-
.../api/model/GetCollectionPropertyResponse.java | 28 ++++++++++
.../model/ListCollectionPropertiesResponse.java | 29 +++++++++++
.../solr/handler/admin/api/CollectionProperty.java | 57 ++++++++++++++++++--
.../apache/solr/cloud/CollectionsAPISolrJTest.java | 42 ++++++++++++---
.../pages/collection-management.adoc | 60 +++++++++++++++++++---
7 files changed, 231 insertions(+), 16 deletions(-)
diff --git a/changelog/unreleased/SOLR-12224-add-collprop-read-apis.yml
b/changelog/unreleased/SOLR-12224-add-collprop-read-apis.yml
new file mode 100644
index 00000000000..3372502b7a4
--- /dev/null
+++ b/changelog/unreleased/SOLR-12224-add-collprop-read-apis.yml
@@ -0,0 +1,8 @@
+# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
+title: Create new v2 APIs for listing and reading collection properties
("collprops")
+type: added # added, changed, fixed, deprecated, removed, dependency_update,
security, other
+authors:
+ - name: Jason Gerlowski
+links:
+ - name: SOLR-12224
+ url: https://issues.apache.org/jira/browse/SOLR-12224
diff --git
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionPropertyApi.java
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionPropertyApi.java
index 8c69aa3ce4c..a4fd54de8db 100644
---
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionPropertyApi.java
+++
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionPropertyApi.java
@@ -18,16 +18,36 @@ package org.apache.solr.client.api.endpoint;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
+import org.apache.solr.client.api.model.GetCollectionPropertyResponse;
+import org.apache.solr.client.api.model.ListCollectionPropertiesResponse;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.client.api.model.UpdateCollectionPropertyRequestBody;
/** V2 API definitions for modifying collection-level properties. */
-@Path("/collections/{collName}/properties/{propName}")
+@Path("/collections/{collName}/properties")
public interface CollectionPropertyApi {
+ @GET
+ @Operation(
+ summary = "List all properties for the specified collection",
+ tags = {"collection-properties"})
+ ListCollectionPropertiesResponse
listCollectionProperties(@PathParam("collName") String collName)
+ throws Exception;
+
+ @GET
+ @Path("/{propName}")
+ @Operation(
+ summary = "Get the value of a specific collection property",
+ tags = {"collection-properties"})
+ GetCollectionPropertyResponse getCollectionProperty(
+ @PathParam("collName") String collName, @PathParam("propName") String
propName)
+ throws Exception;
+
@PUT
+ @Path("/{propName}")
@Operation(
summary = "Create or update a collection property",
tags = {"collection-properties"})
@@ -38,6 +58,7 @@ public interface CollectionPropertyApi {
throws Exception;
@DELETE
+ @Path("/{propName}")
@Operation(
summary = "Delete the specified collection property from the collection",
tags = {"collection-properties"})
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/GetCollectionPropertyResponse.java
b/solr/api/src/java/org/apache/solr/client/api/model/GetCollectionPropertyResponse.java
new file mode 100644
index 00000000000..0da15ee34bb
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/model/GetCollectionPropertyResponse.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/** The Response for the v2 "get collection property" API */
+public class GetCollectionPropertyResponse extends SolrJerseyResponse {
+
+ @Schema(description = "The value of the collection property.")
+ @JsonProperty("value")
+ public String value;
+}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionPropertiesResponse.java
b/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionPropertiesResponse.java
new file mode 100644
index 00000000000..74479ebf914
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionPropertiesResponse.java
@@ -0,0 +1,29 @@
+/*
+ * 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.Map;
+
+/** The Response for the v2 "list collection properties" API */
+public class ListCollectionPropertiesResponse extends SolrJerseyResponse {
+
+ @Schema(description = "The properties for the collection.")
+ @JsonProperty("properties")
+ public Map<String, String> properties;
+}
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionProperty.java
b/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionProperty.java
index 55da1ea0d53..c68e6ce21f2 100644
---
a/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionProperty.java
+++
b/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionProperty.java
@@ -18,9 +18,14 @@
package org.apache.solr.handler.admin.api;
import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
+import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
+import jakarta.inject.Inject;
import java.io.IOException;
+import java.util.Map;
import org.apache.solr.client.api.endpoint.CollectionPropertyApi;
+import org.apache.solr.client.api.model.GetCollectionPropertyResponse;
+import org.apache.solr.client.api.model.ListCollectionPropertiesResponse;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.client.api.model.UpdateCollectionPropertyRequestBody;
import org.apache.solr.common.SolrException;
@@ -31,13 +36,13 @@ import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
/**
- * V2 API implementations for modifying collection-level properties.
+ * V2 API implementations for managing collection-level properties.
*
- * <p>These APIs (PUT and DELETE
/api/collections/collName/properties/propName) are analogous to the
- * v1 /admin/collections?action=COLLECTIONPROP command.
+ * <p>These APIs are analogous to the v1
/admin/collections?action=COLLECTIONPROP command.
*/
public class CollectionProperty extends AdminAPIBase implements
CollectionPropertyApi {
+ @Inject
public CollectionProperty(
CoreContainer coreContainer,
SolrQueryRequest solrQueryRequest,
@@ -45,6 +50,52 @@ public class CollectionProperty extends AdminAPIBase
implements CollectionProper
super(coreContainer, solrQueryRequest, solrQueryResponse);
}
+ @Override
+ @PermissionName(COLL_READ_PERM)
+ public ListCollectionPropertiesResponse listCollectionProperties(String
collName)
+ throws Exception {
+ final var response =
instantiateJerseyResponse(ListCollectionPropertiesResponse.class);
+ ensureRequiredParameterProvided("collName", collName);
+ fetchAndValidateZooKeeperAwareCoreContainer();
+ recordCollectionForLogAndTracing(collName, solrQueryRequest);
+
+ String resolvedCollection =
coreContainer.getAliases().resolveSimpleAlias(collName);
+ CollectionProperties cp =
+ new
CollectionProperties(coreContainer.getZkController().getZkClient());
+ Map<String, String> properties =
cp.getCollectionProperties(resolvedCollection);
+
+ // Handle null case - return empty map instead of null
+ response.properties = (properties != null) ? properties : Map.of();
+
+ return response;
+ }
+
+ @Override
+ @PermissionName(COLL_READ_PERM)
+ public GetCollectionPropertyResponse getCollectionProperty(String collName,
String propName)
+ throws Exception {
+ final var response =
instantiateJerseyResponse(GetCollectionPropertyResponse.class);
+ ensureRequiredParameterProvided("collName", collName);
+ ensureRequiredParameterProvided("propName", propName);
+ fetchAndValidateZooKeeperAwareCoreContainer();
+ recordCollectionForLogAndTracing(collName, solrQueryRequest);
+
+ String resolvedCollection =
coreContainer.getAliases().resolveSimpleAlias(collName);
+ CollectionProperties cp =
+ new
CollectionProperties(coreContainer.getZkController().getZkClient());
+ Map<String, String> properties =
cp.getCollectionProperties(resolvedCollection);
+
+ if (properties != null && properties.containsKey(propName)) {
+ response.value = properties.get(propName);
+ } else {
+ throw new SolrException(
+ SolrException.ErrorCode.NOT_FOUND,
+ "Property '" + propName + "' not found for collection '" + collName
+ "'");
+ }
+
+ return response;
+ }
+
@Override
@PermissionName(COLL_EDIT_PERM)
public SolrJerseyResponse createOrUpdateCollectionProperty(
diff --git
a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
index d4b9148c228..73abeb3042b 100644
--- a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
@@ -50,6 +50,7 @@ import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.CollectionPropertiesApi;
import org.apache.solr.client.solrj.request.CollectionsApi;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.client.solrj.request.CoreStatus;
@@ -544,7 +545,7 @@ public class CollectionsAPISolrJTest extends
SolrCloudTestCase {
}
@Test
- public void testCollectionProp() throws InterruptedException, IOException,
SolrServerException {
+ public void testCollectionProp() throws Exception {
String collectionName = getSaferTestName();
final String propName = "testProperty";
@@ -563,18 +564,47 @@ public class CollectionsAPISolrJTest extends
SolrCloudTestCase {
CollectionAdminRequest.setCollectionProperty(collectionName, propName,
null)
.process(cluster.getSolrClient());
checkCollectionProperty(collectionName, propName, null);
+
+ // Test that "list-properties" returns all properties
+ CollectionAdminRequest.setCollectionProperty(collectionName, propName +
"1", "prop1Val")
+ .process(cluster.getSolrClient());
+ CollectionAdminRequest.setCollectionProperty(collectionName, propName +
"2", "prop2Val")
+ .process(cluster.getSolrClient());
+ final var allProperties =
+ new CollectionPropertiesApi.ListCollectionProperties(collectionName)
+ .process(cluster.getSolrClient())
+ .getParsed()
+ .properties;
+ assertEquals(2, allProperties.size());
+ assertEquals("prop1Val", allProperties.get(propName + "1"));
+ assertEquals("prop2Val", allProperties.get(propName + "2"));
+
+ // Test GET single property API
+ final var prop1Response =
+ new CollectionPropertiesApi.GetCollectionProperty(collectionName,
propName + "1")
+ .process(cluster.getSolrClient())
+ .getParsed();
+ assertEquals("prop1Val", prop1Response.value);
+
+ final var prop2Response =
+ new CollectionPropertiesApi.GetCollectionProperty(collectionName,
propName + "2")
+ .process(cluster.getSolrClient())
+ .getParsed();
+ assertEquals("prop2Val", prop2Response.value);
}
private void checkCollectionProperty(String collection, String propertyName,
String propertyValue)
- throws InterruptedException {
+ throws Exception {
TimeOut timeout = new TimeOut(TIMEOUT, TimeUnit.MILLISECONDS,
TimeSource.NANO_TIME);
while (!timeout.hasTimedOut()) {
- Thread.sleep(10);
- if (Objects.equals(
-
cluster.getZkStateReader().getCollectionProperties(collection).get(propertyName),
- propertyValue)) {
+ final var listCollPropRsp =
+ new CollectionPropertiesApi.ListCollectionProperties(collection)
+ .process(cluster.getSolrClient())
+ .getParsed();
+ if (Objects.equals(listCollPropRsp.properties.get(propertyName),
propertyValue)) {
return;
}
+ Thread.sleep(10);
}
fail("Timed out waiting for cluster property value");
diff --git
a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc
b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc
index 55b3f8b5d38..f743fb4ea8c 100644
---
a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc
+++
b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc
@@ -673,7 +673,8 @@ If the status is anything other than "success", an error
message will explain wh
[[collectionprop]]
== COLLECTIONPROP: Collection Properties
-Add, edit or delete a collection property.
+Add, update, delete, or retrieve collection properties.
+(Listing all collection properties, or fetching an individual property by name
are only supported in Solr's v2 API)
[tabs#collectionproperty-request]
======
@@ -682,7 +683,7 @@ V1 API::
====
[source,bash]
----
-http://localhost:8983/solr/admin/collections?action=COLLECTIONPROP&name=techproducts_v2&propertyName=propertyName&propertyValue=propertyValue
+http://localhost:8983/solr/admin/collections?action=COLLECTIONPROP&name=techproducts&propertyName=propertyName&propertyValue=propertyValue
----
====
@@ -690,20 +691,36 @@ V2 API::
+
====
To create or update a collection property:
+
[source,bash]
----
-curl -X PUT
http://localhost:8983/api/collections/techproducts_v2/properties/foo -H
'Content-Type: application/json' -d '
+curl -X PUT
http://localhost:8983/api/collections/techproducts_v2/properties/propertyName
-H 'Content-Type: application/json' -d '
{
- "value": "bar"
+ "value": "propertyValue"
}
'
----
+To list all properties for a collection:
+
+[source,bash]
+----
+curl http://localhost:8983/api/collections/techproducts/properties
+----
+
+To get a specific collection property by name:
+
+[source,bash]
+----
+curl http://localhost:8983/api/collections/techproducts/properties/propertyName
+----
+
+
To delete an existing collection property:
[source,bash]
----
-curl -X DELETE
http://localhost:8983/api/collections/techproducts_v2/properties/foo
+curl -X DELETE
http://localhost:8983/api/collections/techproducts/properties/propertyName
----
====
======
@@ -742,9 +759,40 @@ When not provided in v1 requests, the property is deleted.
=== COLLECTIONPROP Response
-The response will include the status of the request and the properties that
were updated or removed.
+The response will include the status of the request.
If the status is anything other than "0", an error message will explain why
the request failed.
+For GET requests to list all properties, the response includes a `properties`
object containing all property name-value pairs:
+
+[source,json]
+----
+{
+ "responseHeader": {
+ "status": 0,
+ "QTime": 5
+ },
+ "properties": {
+ "foo": "bar",
+ "myProperty": "myValue"
+ }
+}
+----
+
+For GET requests to retrieve a single property, the response includes a
`value` field with the property value:
+
+[source,json]
+----
+{
+ "responseHeader": {
+ "status": 0,
+ "QTime": 3
+ },
+ "value": "bar"
+}
+----
+
+If a requested property does not exist, the API will return a 404 error with
an appropriate error message.
+
[[migrate]]
== MIGRATE: Migrate Documents to Another Collection