You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ds...@apache.org on 2026/09/07 21:55:55 UTC
(solr) branch branch_10x updated (941a1d90e75 -> c5b2d0e375d)
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a change to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
from 941a1d90e75 SOLR-18417: Deprecate v1 PingRequestHandler and related code (#4872)
new 8695e6db423 Pin locale to deal with flakyness in GCS testing library (#4869)
new ae0c9c40a13 SOLR-18152: improve body format OpenAPI annotations to fully communicate SchemaDesigner API (#4819)
new 73dcc2996c9 Review and tidy solr/modules/sql code (#4840)
new 91908f28a96 SOLR-18410: CrossDC Common: multivalued params and per-entry params don't survive the serialization round-trip (#4867)
new cba01ab8d3b SOLR-16390: Move cluster command-status APIs to /cluster/commands (#4866)
new fa06b3ce19f SOLR-18381: remove ClusterState.getReplicaNamesPerCollectionOnNode (#4768)
new d6f4987d2a4 SOLR-18403: fix snapshot-export to actually use the named snapshot (#4807)
new c68c3cfa33e SlowCompositeReaderWrapper: fix index sort and hasBlocks (#4825)
new c5b2d0e375d SOLR-18413: Handle deleted MIGRATE routing targets (#4862)
The 9 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../SOLR-16390-cluster-command-status.yml | 8 +
...owCompositeReaderWrapper-sort-hasblocks-fix.yml | 6 +
.../fix-dangling-routing-rule-target.yml | 8 +
changelog/unreleased/solr-18410.yml | 8 +
dev-docs/v2-api-conventions.adoc | 2 +
.../client/api/endpoint/ClusterCommandsApi.java | 71 ++++
.../api/endpoint/GetNodeCommandStatusApi.java | 2 +-
.../client/api/endpoint/SchemaDesignerApi.java | 8 +-
...ava => DeleteClusterCommandStatusResponse.java} | 16 +-
.../api/model/GetClusterCommandStatusResponse.java | 97 +++++
.../api/model/SchemaDesignerAddRequestBody.java | 5 -
.../api/model/SchemaDesignerUpdateRequestBody.java | 3 +
.../api/model/UpsertDynamicFieldOperation.java | 3 +
.../client/api/model/UpsertFieldOperation.java | 3 +
.../client/api/model/UpsertFieldTypeOperation.java | 10 +-
.../SchemaChangeOperationSerializationTest.java | 4 +-
.../org/apache/solr/cli/SnapshotExportTool.java | 1 +
.../java/org/apache/solr/cloud/ZkController.java | 13 +-
.../java/org/apache/solr/handler/ClusterAPI.java | 24 --
.../solr/handler/admin/CollectionsHandler.java | 164 +-------
.../solr/handler/admin/api/ClusterCommands.java | 278 +++++++++++++
.../solr/handler/admin/api/UpdateSchema.java | 2 +-
.../solr/index/SlowCompositeReaderWrapper.java | 43 +-
.../java/org/apache/solr/schema/SchemaManager.java | 4 +-
.../processor/DistributedZkUpdateProcessor.java | 95 +++--
.../apache/solr/cli/SnapshotExportToolTest.java | 82 ++++
.../org/apache/solr/cloud/MigrateRouteKeyTest.java | 53 +++
.../org/apache/solr/cloud/ZkControllerTest.java | 9 +-
.../solr/handler/V2ClusterAPIMappingTest.java | 19 -
.../handler/admin/api/ClusterCommandsTest.java | 128 ++++++
.../admin/api/V2UpdateSchemaErrorCaseTests.java | 2 +-
.../handler/designer/TestSchemaDesignerSolrJ.java | 4 +-
.../common/MirroredSolrRequestSerializer.java | 44 +-
.../common/MirroredSolrRequestSerializerTest.java | 111 +++++
.../apache/solr/gcs/GCSBackupRepositoryTest.java | 26 +-
.../solr/gcs/LocalStorageGCSBackupRepository.java | 15 +-
.../apache/solr/handler/sql/CalciteSolrDriver.java | 4 +-
.../org/apache/solr/handler/sql/SolrAggregate.java | 4 +-
.../apache/solr/handler/sql/SolrEnumerator.java | 2 +-
.../org/apache/solr/handler/sql/SolrFilter.java | 18 +-
.../org/apache/solr/handler/sql/SolrSchema.java | 16 +-
.../org/apache/solr/handler/sql/SolrTable.java | 57 +--
.../handler/sql/functions/ArrayContainsAll.java | 4 +-
.../handler/sql/functions/ArrayContainsAny.java | 4 +-
.../solr/handler/sql/SQLWithAuthzEnabledTest.java | 2 +-
.../apache/solr/handler/sql/TestSQLHandler.java | 255 ++++++------
.../solr/handler/sql/TestSQLHandlerNonCloud.java | 2 +-
.../configuration-guide/pages/collections-api.adoc | 9 +-
.../org/apache/solr/common/cloud/ClusterState.java | 18 -
.../solrj/src/resources/java-template/api.mustache | 11 +-
.../solr/webapp/AdminUiSchemaDesignerTest.java | 46 +--
.../web/js/angular/controllers/schema-designer.js | 460 ++++++++++++---------
solr/webapp/web/js/angular/services.js | 17 +-
solr/webapp/web/partials/schema-designer.html | 4 +-
54 files changed, 1549 insertions(+), 755 deletions(-)
create mode 100644 changelog/unreleased/SOLR-16390-cluster-command-status.yml
create mode 100644 changelog/unreleased/SlowCompositeReaderWrapper-sort-hasblocks-fix.yml
create mode 100644 changelog/unreleased/fix-dangling-routing-rule-target.yml
create mode 100644 changelog/unreleased/solr-18410.yml
create mode 100644 solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterCommandsApi.java
copy solr/api/src/java/org/apache/solr/client/api/model/{ClusterPropertyDetails.java => DeleteClusterCommandStatusResponse.java} (74%)
create mode 100644 solr/api/src/java/org/apache/solr/client/api/model/GetClusterCommandStatusResponse.java
create mode 100644 solr/core/src/java/org/apache/solr/handler/admin/api/ClusterCommands.java
create mode 100644 solr/core/src/test/org/apache/solr/cli/SnapshotExportToolTest.java
create mode 100644 solr/core/src/test/org/apache/solr/handler/admin/api/ClusterCommandsTest.java
(solr) 03/09: Review and tidy solr/modules/sql code (#4840)
Posted by ds...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
commit 73dcc2996c953edc98467113fd579e8f13aae7ee
Author: Eric Pugh <ep...@opensourceconnections.com>
AuthorDate: Fri Sep 4 13:22:18 2026 -0400
Review and tidy solr/modules/sql code (#4840)
(cherry picked from commit 87d5f62af2547188586b9fd123196d44a167ece5)
---
.../apache/solr/handler/sql/CalciteSolrDriver.java | 4 +-
.../org/apache/solr/handler/sql/SolrAggregate.java | 4 +-
.../apache/solr/handler/sql/SolrEnumerator.java | 2 +-
.../org/apache/solr/handler/sql/SolrFilter.java | 18 +-
.../org/apache/solr/handler/sql/SolrSchema.java | 16 +-
.../org/apache/solr/handler/sql/SolrTable.java | 57 ++---
.../handler/sql/functions/ArrayContainsAll.java | 4 +-
.../handler/sql/functions/ArrayContainsAny.java | 4 +-
.../solr/handler/sql/SQLWithAuthzEnabledTest.java | 2 +-
.../apache/solr/handler/sql/TestSQLHandler.java | 255 ++++++++++-----------
.../solr/handler/sql/TestSQLHandlerNonCloud.java | 2 +-
11 files changed, 170 insertions(+), 198 deletions(-)
diff --git a/solr/modules/sql/src/java/org/apache/solr/handler/sql/CalciteSolrDriver.java b/solr/modules/sql/src/java/org/apache/solr/handler/sql/CalciteSolrDriver.java
index 52e1fdc39d7..8e7c57fc3bc 100644
--- a/solr/modules/sql/src/java/org/apache/solr/handler/sql/CalciteSolrDriver.java
+++ b/solr/modules/sql/src/java/org/apache/solr/handler/sql/CalciteSolrDriver.java
@@ -75,8 +75,8 @@ public class CalciteSolrDriver extends Driver {
Hook.SQL2REL_CONVERTER_CONFIG_BUILDER.addThread(CalciteSolrDriver::subQueryThreshold);
// disable Calcite's simplify (see SOLR-16009) as it erases some query
- // constructs that are still meaningful to Solr (such as AND'd filters on the same field,
- // which works for multi-valued fields in Solr but looks like nonsense to Calcite.
+ // constructs that are still meaningful to Solr (such as AND'd filters on the same field),
+ // which works for multivalued fields in Solr but looks like nonsense to Calcite.
Hook.REL_BUILDER_SIMPLIFY.addThread(CalciteSolrDriver::relBuilderSimplify);
Connection connection = super.connect(url, info);
diff --git a/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrAggregate.java b/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrAggregate.java
index 1e3c2f7e226..293d63e787a 100644
--- a/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrAggregate.java
+++ b/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrAggregate.java
@@ -48,7 +48,7 @@ class SolrAggregate extends Aggregate implements SolrRel {
// Returns the Solr agg metric identifier (includes column) for the SQL metric
static String solrAggMetricId(String metric, String column) {
- // CountDistinctMetric's getIdentifer returns "countDist" but all others return a lowercased
+ // CountDistinctMetric's getIdentifier returns "countDist" but all others return a lowercased
// value
String funcName =
COUNT_DISTINCT.equals(metric) ? COUNT_DISTINCT : metric.toLowerCase(Locale.ROOT);
@@ -118,7 +118,7 @@ class SolrAggregate extends Aggregate implements SolrRel {
return new Pair<>(aggregation.getName(), "*");
}
case 1:
- String inName = inNames.get(args.get(0));
+ String inName = inNames.get(args.getFirst());
String name = implementor.fieldMappings.getOrDefault(inName, inName);
if (SUPPORTED_AGGREGATIONS.contains(aggregation)) {
return new Pair<>(aggregation.getName(), name);
diff --git a/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrEnumerator.java b/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrEnumerator.java
index cdd553fdbf8..352e5713c83 100644
--- a/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrEnumerator.java
+++ b/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrEnumerator.java
@@ -62,7 +62,7 @@ class SolrEnumerator implements Enumerator<Object> {
@Override
public Object current() {
if (fields.size() == 1) {
- return this.getter(current, fields.get(0));
+ return this.getter(current, fields.getFirst());
} else {
// Build an array with all fields in this row
Object[] row = new Object[fields.size()];
diff --git a/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrFilter.java b/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrFilter.java
index cbc0cf7dfc0..b8a326f6241 100644
--- a/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrFilter.java
+++ b/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrFilter.java
@@ -271,7 +271,7 @@ class SolrFilter extends Filter implements SolrRel {
throw new AssertionError("expected 1 operand for " + node);
}
- final RexNode left = operands.get(0);
+ final RexNode left = operands.getFirst();
if (left instanceof RexInputRef) {
String name = fieldNames.get(((RexInputRef) left).getIndex());
SqlKind kind = node.getKind();
@@ -325,23 +325,23 @@ class SolrFilter extends Filter implements SolrRel {
}
}
- String query = "";
+ StringBuilder query = new StringBuilder();
if (!andStrings.isEmpty()) {
String andString = String.join(" AND ", andStrings);
- query += "(" + andString + ")";
+ query.append("(").append(andString).append(")");
}
if (!notStrings.isEmpty()) {
if (!query.isEmpty()) {
- query += " AND ";
+ query.append(" AND ");
}
for (int i = 0; i < notStrings.size(); i++) {
if (i > 0) {
- query += " AND ";
+ query.append(" AND ");
}
- query += " (*:* -" + notStrings.get(i) + ")";
+ query.append(" (*:* -").append(notStrings.get(i)).append(")");
}
}
- return query.trim();
+ return query.toString().trim();
} else {
return String.join(" AND ", andStrings);
}
@@ -402,7 +402,7 @@ class SolrFilter extends Filter implements SolrRel {
protected String translateComparison(RexNode node) {
final SqlKind kind = node.getKind();
if (kind == SqlKind.NOT) {
- RexNode negated = ((RexCall) node).getOperands().get(0);
+ RexNode negated = ((RexCall) node).getOperands().getFirst();
if (negated.isA(SqlKind.AND)) {
AndClause andClause = translateAndOrBetween(negated, true);
// if the resulting andClause is a "between" then don't negate it as it's already
@@ -789,7 +789,7 @@ class SolrFilter extends Filter implements SolrRel {
notBuilder.append(")");
}
- return "and(" + builder.toString() + "," + notBuilder.toString() + ")";
+ return "and(" + builder + "," + notBuilder + ")";
} else {
return builder.toString();
}
diff --git a/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrSchema.java b/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrSchema.java
index 702f0b756eb..d9eee8c3622 100644
--- a/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrSchema.java
+++ b/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrSchema.java
@@ -84,10 +84,6 @@ class SolrSchema extends AbstractSchema implements Closeable {
isClosed = true;
}
- public boolean isClosed() {
- return isClosed;
- }
-
@Override
protected Map<String, Table> getTableMap() {
CloudSolrClient cloudSolrClient = solrClientCache.getCloudSolrClient(solrConnection);
@@ -160,8 +156,8 @@ class SolrSchema extends AbstractSchema implements Closeable {
RelDataType buildRowSchema(String collection) {
// Temporary type factory, just for the duration of this method. Allowable
- // because we're creating a proto-type, not a type; before being used, the
- // proto-type will be copied into a real type factory.
+ // because we're creating a prototype, not a type; before being used, the
+ // prototype will be copied into a real type factory.
final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
final RelDataTypeFactory.Builder fieldInfo = typeFactory.builder();
@@ -201,12 +197,12 @@ class SolrSchema extends AbstractSchema implements Closeable {
RelDataType type;
- // We have to pass multi-valued fields through Calcite as SQL Type ANY
+ // We have to pass multivalued fields through Calcite as SQL Type ANY
// Array doesn't work for aggregations! Calcite doesn't like GROUP BY on an ARRAY field
- // but Solr happily computes aggs on a multi-valued field, so we have a paradigm mis-match and
- // ANY is the best way to retain use of operators on multi-valued fields while still being
+ // but Solr happily computes aggs on a multivalued field, so we have a paradigm mismatch and
+ // ANY is the best way to retain use of operators on multivalued fields while still being
// able
- // to GROUP BY and project the multi-valued fields in results
+ // to GROUP BY and project the multivalued fields in results
EnumSet<FieldFlag> flags = getFieldFlags(luceneFieldInfo);
if (flags != null && flags.contains(FieldFlag.MULTI_VALUED)) {
type = typeFactory.createSqlType(SqlTypeName.ANY);
diff --git a/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrTable.java b/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrTable.java
index 84180a8f0df..fa2466b8eaf 100644
--- a/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrTable.java
+++ b/solr/modules/sql/src/java/org/apache/solr/handler/sql/SolrTable.java
@@ -29,7 +29,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
-import java.util.stream.Collectors;
import org.apache.calcite.adapter.java.AbstractQueryableTable;
import org.apache.calcite.linq4j.AbstractEnumerable;
import org.apache.calcite.linq4j.Enumerable;
@@ -143,7 +142,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
boolean mapReduce = "map_reduce".equals(properties.getProperty("aggregationMode"));
boolean negative = Boolean.parseBoolean(negativeQuery);
- String q = null;
+ String q;
if (query == null) {
q = DEFAULT_QUERY;
@@ -212,22 +211,6 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
};
}
- private static StreamComparator bucketSortComp(List<Bucket> buckets, Map<String, String> dirs) {
- FieldComparator[] comps = new FieldComparator[buckets.size()];
- for (int i = 0; i < buckets.size(); i++) {
- ComparatorOrder comparatorOrder =
- ComparatorOrder.fromString(dirs.get(buckets.get(i).toString()));
- String sortKey = buckets.get(i).toString();
- comps[i] = new FieldComparator(sortKey, comparatorOrder);
- }
-
- if (comps.length == 1) {
- return comps[0];
- } else {
- return new MultipleFieldComparator(comps);
- }
- }
-
private static StreamComparator bucketSortComp(Bucket[] buckets, String dir) {
FieldComparator[] comps = new FieldComparator[buckets.length];
for (int i = 0; i < buckets.length; i++) {
@@ -267,8 +250,8 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
private List<Metric> buildMetrics(List<Pair<String, String>> metricPairs, boolean ifEmptyCount) {
List<Metric> metrics = new ArrayList<>(metricPairs.size());
- metrics.addAll(metricPairs.stream().map(this::getMetric).collect(Collectors.toList()));
- if (metrics.size() == 0 && ifEmptyCount) {
+ metrics.addAll(metricPairs.stream().map(this::getMetric).toList());
+ if (metrics.isEmpty() && ifEmptyCount) {
metrics.add(new CountMetric());
}
return metrics;
@@ -376,7 +359,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
private String getSort(List<Pair<String, String>> orders) {
StringBuilder buf = new StringBuilder();
for (Pair<String, String> pair : orders) {
- if (buf.length() > 0) {
+ if (!buf.isEmpty()) {
buf.append(",");
}
buf.append(pair.getKey()).append(" ").append(pair.getValue());
@@ -385,17 +368,11 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
return buf.toString();
}
- private String getSingleSort(Pair<String, String> order) {
- StringBuilder buf = new StringBuilder();
- buf.append(order.getKey()).append(" ").append(order.getValue());
- return buf.toString();
- }
-
private String getFields(List<Map.Entry<String, Class<?>>> fields) {
StringBuilder buf = new StringBuilder();
for (Map.Entry<String, Class<?>> field : fields) {
- if (buf.length() > 0) {
+ if (!buf.isEmpty()) {
buf.append(",");
}
@@ -409,7 +386,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
StringBuilder buf = new StringBuilder();
for (String field : fieldSet) {
- if (buf.length() > 0) {
+ if (!buf.isEmpty()) {
buf.append(",");
}
@@ -437,7 +414,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
}
private static String getSortDirection(List<Pair<String, String>> orders) {
- if (orders != null && orders.size() > 0) {
+ if (orders != null && !orders.isEmpty()) {
for (Pair<String, String> item : orders) {
return item.getValue();
}
@@ -533,7 +510,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
Set<String> fieldSet = getFieldSet(metrics, fields);
if (metrics.length == 0) {
- throw new IOException("Group by queries must include atleast one aggregate function.");
+ throw new IOException("Group by queries must include at least one aggregate function.");
}
String fl = getFields(fieldSet);
@@ -552,7 +529,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
params.set(SORT, sort);
- TupleStream tupleStream = null;
+ TupleStream tupleStream;
// Always use the /export handler for Group By Queries because it requires exporting full
// result sets.
@@ -603,7 +580,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
// We need to push down the having clause to ensure that LIMIT does not cut off records
// prior to the having filter.
- if (orders != null && orders.size() > 0) {
+ if (orders != null && !orders.isEmpty()) {
if (!sortsEqual(buckets, sortDirection, orders)) {
int lim = (limit == null) ? 100 : Integer.parseInt(limit);
StreamComparator comp = getComp(orders);
@@ -678,9 +655,9 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
int limit = lim != null ? Integer.parseInt(lim) : 1000;
- FieldComparator[] sorts = null;
+ FieldComparator[] sorts;
- if (orders == null || orders.size() == 0) {
+ if (orders == null || orders.isEmpty()) {
sorts = new FieldComparator[buckets.length];
for (int i = 0; i < sorts.length; i++) {
sorts[i] = new FieldComparator("index", ComparatorOrder.ASCENDING);
@@ -743,11 +720,11 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
String fl = getFields(fields);
- String sort = null;
- StreamEqualitor ecomp = null;
- StreamComparator comp = null;
+ String sort;
+ StreamEqualitor ecomp;
+ StreamComparator comp;
- if (orders != null && orders.size() > 0) {
+ if (orders != null && !orders.isEmpty()) {
StreamComparator[] adjustedSorts = adjustSorts(orders, buckets);
// Because of the way adjustSorts works we know that each FieldComparator has a single
// field name. For this reason we can just look at the leftFieldName
@@ -810,7 +787,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
params.set(SORT, sort);
- TupleStream tupleStream = null;
+ TupleStream tupleStream;
// Always use the /export handler for Distinct Queries because it requires exporting full
// result sets.
diff --git a/solr/modules/sql/src/java/org/apache/solr/handler/sql/functions/ArrayContainsAll.java b/solr/modules/sql/src/java/org/apache/solr/handler/sql/functions/ArrayContainsAll.java
index c50e4840c26..88b94003ae6 100644
--- a/solr/modules/sql/src/java/org/apache/solr/handler/sql/functions/ArrayContainsAll.java
+++ b/solr/modules/sql/src/java/org/apache/solr/handler/sql/functions/ArrayContainsAll.java
@@ -18,9 +18,9 @@
package org.apache.solr.handler.sql.functions;
/**
- * Operator for filtering on Solr multi-valued fields with 'AND" clause. Example:
+ * Operator for filtering on Solr multivalued fields with "AND" clause. Example:
* ARRAY_CONTAINS_ALL(field, ('val1', 'val2')) will be transformed to filter query field:("val1" AND
- * "val2")
+ * "val2").
*/
public class ArrayContainsAll extends ArrayContains {
private static final String UDF_NAME = "ARRAY_CONTAINS_ALL";
diff --git a/solr/modules/sql/src/java/org/apache/solr/handler/sql/functions/ArrayContainsAny.java b/solr/modules/sql/src/java/org/apache/solr/handler/sql/functions/ArrayContainsAny.java
index 7248d89da4c..df80fdbc45c 100644
--- a/solr/modules/sql/src/java/org/apache/solr/handler/sql/functions/ArrayContainsAny.java
+++ b/solr/modules/sql/src/java/org/apache/solr/handler/sql/functions/ArrayContainsAny.java
@@ -18,9 +18,9 @@
package org.apache.solr.handler.sql.functions;
/**
- * Operator for filtering on Solr multi-valued fields with 'OR" clause. Example:
+ * Operator for filtering on Solr multivalued fields with "OR" clause. Example:
* ARRAY_CONTAINS_ALL(field, ('val1', 'val2')) will be transformed to filter query field:("val1" OR
- * "val2")
+ * "val2").
*/
public class ArrayContainsAny extends ArrayContains {
private static final String UDF_NAME = "ARRAY_CONTAINS_ANY";
diff --git a/solr/modules/sql/src/test/org/apache/solr/handler/sql/SQLWithAuthzEnabledTest.java b/solr/modules/sql/src/test/org/apache/solr/handler/sql/SQLWithAuthzEnabledTest.java
index 4ec16f671b4..1101926ed94 100644
--- a/solr/modules/sql/src/test/org/apache/solr/handler/sql/SQLWithAuthzEnabledTest.java
+++ b/solr/modules/sql/src/test/org/apache/solr/handler/sql/SQLWithAuthzEnabledTest.java
@@ -108,7 +108,7 @@ public class SQLWithAuthzEnabledTest extends SolrCloudTestCase {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set("stmt", "select id from " + collectionName);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrStream solrStream = new SolrStream(baseUrl, collectionName, "/sql", params);
solrStream.setCredentials(SAD_USER, PASS);
diff --git a/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandler.java b/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandler.java
index 1fd73e7a61e..e130403d566 100644
--- a/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandler.java
+++ b/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandler.java
@@ -48,7 +48,6 @@ import org.junit.Test;
public class TestSQLHandler extends SolrCloudTestCase {
private static final String COLLECTIONORALIAS = "collection1";
- private static final int TIMEOUT = DEFAULT_TIMEOUT;
private static final String id = "id";
private static boolean useAlias;
@@ -210,83 +209,83 @@ public class TestSQLHandler extends SolrCloudTestCase {
"stmt",
"select id, field_i, str_s, field_f, field_d, field_l from collection1 where (text_t='(XXXX)' OR text_t='XXXX') AND text_t='XXXX' order by field_i desc");
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
List<Tuple> tuples = getTuples(sParams, baseUrl);
assertEquals(8, tuples.size());
Tuple tuple;
- tuple = tuples.get(0);
- assertEquals(tuple.getLong("id").longValue(), 8);
- assertEquals(tuple.getLong("field_i").longValue(), 60);
+ tuple = tuples.getFirst();
+ assertEquals(8, tuple.getLong("id").longValue());
+ assertEquals(60, tuple.getLong("field_i").longValue());
assertEquals("c", tuple.get("str_s"));
- assertEquals(tuple.getLong("field_i").longValue(), 60L);
- assertEquals(tuple.getDouble("field_f"), 60.5, 0.0);
- assertEquals(tuple.getDouble("field_d"), 60.5, 0.0);
- assertEquals(tuple.getLong("field_l").longValue(), 60);
+ assertEquals(60L, tuple.getLong("field_i").longValue());
+ assertEquals(60.5, tuple.getDouble("field_f"), 0.0);
+ assertEquals(60.5, tuple.getDouble("field_d"), 0.0);
+ assertEquals(60, tuple.getLong("field_l").longValue());
tuple = tuples.get(1);
- assertEquals(tuple.getLong("id").longValue(), 7);
- assertEquals(tuple.getLong("field_i").longValue(), 50);
+ assertEquals(7, tuple.getLong("id").longValue());
+ assertEquals(50, tuple.getLong("field_i").longValue());
assertEquals("c", tuple.get("str_s"));
- assertEquals(tuple.getLong("field_i").longValue(), 50);
- assertEquals(tuple.getDouble("field_f"), 50.5, 0.0);
- assertEquals(tuple.getDouble("field_d"), 50.5, 0.0);
- assertEquals(tuple.getLong("field_l").longValue(), 50);
+ assertEquals(50, tuple.getLong("field_i").longValue());
+ assertEquals(50.5, tuple.getDouble("field_f"), 0.0);
+ assertEquals(50.5, tuple.getDouble("field_d"), 0.0);
+ assertEquals(50, tuple.getLong("field_l").longValue());
tuple = tuples.get(2);
- assertEquals(tuple.getLong("id").longValue(), 6);
- assertEquals(tuple.getLong("field_i").longValue(), 40);
+ assertEquals(6, tuple.getLong("id").longValue());
+ assertEquals(40, tuple.getLong("field_i").longValue());
assertEquals("c", tuple.get("str_s"));
- assertEquals(tuple.getLong("field_i").longValue(), 40);
- assertEquals(tuple.getDouble("field_f"), 40.5, 0.0);
- assertEquals(tuple.getDouble("field_d"), 40.5, 0.0);
- assertEquals(tuple.getLong("field_l").longValue(), 40);
+ assertEquals(40, tuple.getLong("field_i").longValue());
+ assertEquals(40.5, tuple.getDouble("field_f"), 0.0);
+ assertEquals(40.5, tuple.getDouble("field_d"), 0.0);
+ assertEquals(40, tuple.getLong("field_l").longValue());
tuple = tuples.get(3);
- assertEquals(tuple.getLong("id").longValue(), 5);
- assertEquals(tuple.getLong("field_i").longValue(), 30);
+ assertEquals(5, tuple.getLong("id").longValue());
+ assertEquals(30, tuple.getLong("field_i").longValue());
assertEquals("c", tuple.get("str_s"));
- assertEquals(tuple.getLong("field_i").longValue(), 30);
- assertEquals(tuple.getDouble("field_f"), 30.5, 0.0);
- assertEquals(tuple.getDouble("field_d"), 30.5, 0.0);
- assertEquals(tuple.getLong("field_l").longValue(), 30);
+ assertEquals(30, tuple.getLong("field_i").longValue());
+ assertEquals(30.5, tuple.getDouble("field_f"), 0.0);
+ assertEquals(30.5, tuple.getDouble("field_d"), 0.0);
+ assertEquals(30, tuple.getLong("field_l").longValue());
tuple = tuples.get(4);
- assertEquals(tuple.getLong("id").longValue(), 3);
- assertEquals(tuple.getLong("field_i").longValue(), 20);
+ assertEquals(3, tuple.getLong("id").longValue());
+ assertEquals(20, tuple.getLong("field_i").longValue());
assertEquals("a", tuple.get("str_s"));
- assertEquals(tuple.getLong("field_i").longValue(), 20);
- assertEquals(tuple.getDouble("field_f"), 20.5, 0.0);
- assertEquals(tuple.getDouble("field_d"), 20.5, 0.0);
- assertEquals(tuple.getLong("field_l").longValue(), 20);
+ assertEquals(20, tuple.getLong("field_i").longValue());
+ assertEquals(20.5, tuple.getDouble("field_f"), 0.0);
+ assertEquals(20.5, tuple.getDouble("field_d"), 0.0);
+ assertEquals(20, tuple.getLong("field_l").longValue());
tuple = tuples.get(5);
- assertEquals(tuple.getLong("id").longValue(), 4);
- assertEquals(tuple.getLong("field_i").longValue(), 11);
+ assertEquals(4, tuple.getLong("id").longValue());
+ assertEquals(11, tuple.getLong("field_i").longValue());
assertEquals("b", tuple.get("str_s"));
- assertEquals(tuple.getLong("field_i").longValue(), 11);
- assertEquals(tuple.getDouble("field_f"), 11.5, 0.0);
- assertEquals(tuple.getDouble("field_d"), 11.5, 0.0);
- assertEquals(tuple.getLong("field_l").longValue(), 11);
+ assertEquals(11, tuple.getLong("field_i").longValue());
+ assertEquals(11.5, tuple.getDouble("field_f"), 0.0);
+ assertEquals(11.5, tuple.getDouble("field_d"), 0.0);
+ assertEquals(11, tuple.getLong("field_l").longValue());
tuple = tuples.get(6);
- assertEquals(tuple.getLong("id").longValue(), 2);
- assertEquals(tuple.getLong("field_i").longValue(), 8);
+ assertEquals(2, tuple.getLong("id").longValue());
+ assertEquals(8, tuple.getLong("field_i").longValue());
assertEquals("b", tuple.get("str_s"));
- assertEquals(tuple.getLong("field_i").longValue(), 8);
- assertEquals(tuple.getDouble("field_f"), 8.5, 0.0);
- assertEquals(tuple.getDouble("field_d"), 8.5, 0.0);
- assertEquals(tuple.getLong("field_l").longValue(), 8);
+ assertEquals(8, tuple.getLong("field_i").longValue());
+ assertEquals(8.5, tuple.getDouble("field_f"), 0.0);
+ assertEquals(8.5, tuple.getDouble("field_d"), 0.0);
+ assertEquals(8, tuple.getLong("field_l").longValue());
tuple = tuples.get(7);
- assertEquals(tuple.getLong("id").longValue(), 1);
- assertEquals(tuple.getLong("field_i").longValue(), 7);
+ assertEquals(1, tuple.getLong("id").longValue());
+ assertEquals(7, tuple.getLong("field_i").longValue());
assertEquals("a", tuple.get("str_s"));
- assertEquals(tuple.getLong("field_i").longValue(), 7);
- assertEquals(tuple.getDouble("field_f"), 7.5, 0.0);
- assertEquals(tuple.getDouble("field_d"), 7.5, 0.0);
- assertEquals(tuple.getLong("field_l").longValue(), 7);
+ assertEquals(7, tuple.getLong("field_i").longValue());
+ assertEquals(7.5, tuple.getDouble("field_f"), 0.0);
+ assertEquals(7.5, tuple.getDouble("field_d"), 0.0);
+ assertEquals(7, tuple.getLong("field_l").longValue());
// Assert field order
// assertResponseContains(clients.get(0), sParams,
@@ -301,7 +300,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(8, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(8, tuple.getLong("id").longValue());
assertEquals(60, tuple.getLong("field_i").longValue());
assertEquals("c", tuple.get("str_s"));
@@ -350,7 +349,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(1, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(8, tuple.getLong("id").longValue());
assertEquals(60, tuple.getLong("field_i").longValue());
assertEquals("c", tuple.get("str_s"));
@@ -364,7 +363,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(3, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(3, tuple.getLong("id").longValue());
assertEquals(20, tuple.getLong("field_i").longValue());
assertEquals("a", tuple.get("str_s"));
@@ -388,7 +387,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(3, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(3, tuple.getLong("myId").longValue());
assertEquals(20, tuple.getLong("myInt").longValue());
assertEquals("a", tuple.get("myString"));
@@ -412,7 +411,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(3, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(3, tuple.getLong("myId").longValue());
assertEquals(20, tuple.getLong("myInt").longValue());
assertEquals("a", tuple.get("myString"));
@@ -446,7 +445,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
tuples = getTuples(sParams, baseUrl);
assertEquals(1, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(8, tuple.getLong("id").longValue());
assertEquals(60, tuple.getLong("field_i").longValue());
assertEquals("c", tuple.get("str_s"));
@@ -470,7 +469,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
.add("id", "1", "text_t", "XXXX XXXX", "str_s", "a", "field_i", "7")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrParams sParams =
params("caseSensitive", "true", "stmt", "select id, FIELD_I from collection1 limit 1");
@@ -547,7 +546,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
"witha\"quote")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
// Equals
SolrParams sParams = params("stmt", "select id from collection1 where id = 1 order by id asc");
@@ -556,7 +555,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(1, tuples.size());
- Tuple tuple = tuples.get(0);
+ Tuple tuple = tuples.getFirst();
assertEquals("1", tuple.get("id"));
// Not Equals <>
@@ -566,7 +565,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(7, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("2", tuple.get("id"));
tuple = tuples.get(1);
assertEquals("3", tuple.get("id"));
@@ -590,7 +589,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
//
// assertEquals(7, tuples.size());
//
- // tuple = tuples.get(0);
+ // tuple = tuples.getFirst();
// assertEquals(2L, tuple.get("id"));
// tuple = tuples.get(1);
// assertEquals(3L, tuple.get("id"));
@@ -612,7 +611,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(1, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("1", tuple.get("id"));
// Less than equal
@@ -622,7 +621,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(2, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("1", tuple.get("id"));
tuple = tuples.get(1);
assertEquals("2", tuple.get("id"));
@@ -634,7 +633,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(1, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("8", tuple.get("id"));
// Greater than equal
@@ -644,7 +643,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(2, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("7", tuple.get("id"));
tuple = tuples.get(1);
assertEquals("8", tuple.get("id"));
@@ -673,7 +672,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
.add("id", "8", "Text_t", "XXXX XXXX", "Str_s", "c", "Field_i", "60")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrParams sParams =
params(
@@ -688,7 +687,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
Tuple tuple;
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(8, tuple.getLong("id").longValue());
assertEquals(60, tuple.getLong("Field_i").longValue());
assertEquals("c", tuple.get("Str_s"));
@@ -738,7 +737,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(tuples.toString(), 2, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("Str_s"));
assertEquals(60, tuple.getDouble("EXPR$1"), 0.0);
@@ -757,7 +756,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(2, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("Str_s"));
assertEquals(60, tuple.getDouble("EXPR$1"), 0.0);
@@ -780,7 +779,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
.add("id", "8", "text_t", "XXXX XXXX", "str_s", "c", "field_i", "60")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrParams sParams =
params(
@@ -796,7 +795,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
Tuple tuple;
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("a", tuple.get("str_s"));
assertEquals(1, tuple.getLong("field_i").longValue());
@@ -832,7 +831,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("str_s"));
assertEquals(60, tuple.getLong("field_i").longValue());
@@ -868,7 +867,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("myString"));
assertEquals(60, tuple.getLong("myInt").longValue());
@@ -904,7 +903,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(2, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("str_s"));
assertEquals(60, tuple.getLong("field_i").longValue());
@@ -921,7 +920,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("a", tuple.get("str_s"));
assertEquals(1, tuple.getLong("field_i").longValue());
@@ -962,7 +961,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(2, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("a", tuple.get("str_s"));
assertEquals(1, tuple.getLong("field_i").longValue());
@@ -985,7 +984,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
.add("id", "8", "text_t", "XXXX XXXX", "str_s", "c", "field_i", "60")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrParams sParams =
params(
@@ -997,7 +996,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
List<Tuple> tuples = getTuples(sParams, baseUrl);
assertEquals(6, tuples.size());
- Tuple tuple = tuples.get(0);
+ Tuple tuple = tuples.getFirst();
assertEquals("a", tuple.get("str_s"));
assertEquals(1, tuple.getLong("field_i").longValue());
@@ -1033,7 +1032,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("str_s"));
assertEquals(60, tuple.getLong("field_i").longValue());
@@ -1068,7 +1067,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("myString"));
assertEquals(60, tuple.getLong("field_i").longValue());
@@ -1104,7 +1103,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(2, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("str_s"));
assertEquals(60, tuple.getLong("field_i").longValue());
@@ -1124,7 +1123,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("a", tuple.get("str_s"));
assertEquals(1, tuple.getLong("field_i").longValue());
@@ -1159,7 +1158,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
tuples = getTuples(sParams, baseUrl);
assertEquals(tuples.toString(), 2, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("a", tuple.get("str_s"));
assertEquals(1, tuple.getLong("field_i").longValue());
@@ -1182,7 +1181,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
.add("id", "8", "text_t", "XXXX XXXX", "str_s", "c", "field_i", "60")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrParams sParams =
params(
"numWorkers",
@@ -1198,7 +1197,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
Tuple tuple;
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("a", tuple.get("str_s"));
assertEquals(1, tuple.getLong("field_i").longValue());
@@ -1236,7 +1235,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("str_s"));
assertEquals(60, tuple.getLong("field_i").longValue());
@@ -1274,7 +1273,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("myString"));
assertEquals(60, tuple.getLong("field_i").longValue());
@@ -1312,7 +1311,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(2, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("str_s"));
assertEquals(60, tuple.getLong("field_i").longValue());
@@ -1334,7 +1333,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("a", tuple.get("str_s"));
assertEquals(1, tuple.getLong("field_i").longValue());
@@ -1372,7 +1371,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(2, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("a", tuple.get("str_s"));
assertEquals(1, tuple.getLong("field_i").longValue());
@@ -1396,7 +1395,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
.add("id", "9", "text_t", "XXXX XXXY", "str_s", "d", "field_i", "70")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrParams sParams =
params(
@@ -1414,7 +1413,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
Tuple tuple;
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("b", tuple.get("str_s"));
assertEquals(2, tuple.getDouble("EXPR$1"), 0.0); // count(*)
assertEquals(19, tuple.getDouble("EXPR$2"), 0.0); // sum(field_i)
@@ -1444,7 +1443,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
// Only two results because of the limit.
assertEquals(2, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("b", tuple.get("str_s"));
assertEquals(2, tuple.getDouble("EXPR$1"), 0.0); // count(*)
assertEquals(19, tuple.getDouble("EXPR$2"), 0.0); // sum(field_i)
@@ -1476,7 +1475,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(tuples.toString(), 3, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("str_s"));
assertEquals(4, tuple.getDouble("EXPR$1"), 0.0); // count(*)
assertEquals(180, tuple.getDouble("EXPR$2"), 0.0); // sum(field_i)
@@ -1516,7 +1515,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(3, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("c", tuple.get("myString"));
assertEquals(4, tuple.getDouble("EXPR$1"), 0.0); // count(*)
assertEquals(180, tuple.getDouble("mySum"), 0.0);
@@ -1552,7 +1551,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(1, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("b", tuple.get("str_s"));
assertEquals(2, tuple.getDouble("EXPR$1"), 0.0); // count(*)
assertEquals(19, tuple.getDouble("EXPR$2"), 0.0); // sum(field_i)
@@ -1573,7 +1572,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(1, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("b", tuple.get("str_s"));
assertEquals(2, tuple.getDouble("EXPR$1"), 0.0); // count(*)
assertEquals(19, tuple.getDouble("EXPR$2"), 0.0); // sum(field_i)
@@ -1594,7 +1593,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(1, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals("b", tuple.get("str_s"));
assertEquals(2, tuple.getDouble("EXPR$1"), 0.0); // count(*)
assertEquals(19, tuple.getDouble("mySum"), 0.0);
@@ -1632,7 +1631,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
.add(id, "9", "a_s", "hello0", "a_i", "14", "a_f", "10")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrParams sParams =
params(
@@ -1646,7 +1645,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
// Test Long and Double Sums
- Tuple tuple = tuples.get(0);
+ Tuple tuple = tuples.getFirst();
Double count = tuple.getDouble("EXPR$0"); // count(*)
Double sumi = tuple.getDouble("EXPR$1"); // sum(a_i)
@@ -1680,7 +1679,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
// Test Long and Double Sums
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
count = tuple.getDouble("myCount");
sumi = tuple.getDouble("mySum");
@@ -1715,7 +1714,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
// Test Long and Double Sums
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
count = tuple.getDouble("myCount");
sumi = tuple.getDouble("mySum");
@@ -1749,7 +1748,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(1, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
count = tuple.getDouble("EXPR$0"); // count(*)
sumi = tuple.getDouble("EXPR$1"); // sum(a_i)
@@ -1782,7 +1781,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(1, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
count = tuple.getDouble("EXPR$0"); // count(*)
sumi = tuple.getDouble("EXPR$1"); // sum(a_i)
@@ -1829,7 +1828,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
.add(id, "8", "year_i", "2014", "month_i", "4", "day_i", "2", "item_i", "1")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrParams sParams =
params(
@@ -1844,7 +1843,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
Tuple tuple;
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(2015, tuple.getLong("year_i").longValue());
assertEquals(66, tuple.getDouble("EXPR$1"), 0.0); // sum(item_i)
@@ -1862,7 +1861,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(3, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(2015, tuple.getLong("year_i").longValue());
assertEquals(11, tuple.getLong("month_i").longValue());
assertEquals(57, tuple.getDouble("EXPR$2"), 0.0); // sum(item_i)
@@ -1887,7 +1886,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(2015, tuple.getLong("year_i").longValue());
assertEquals(11, tuple.getLong("month_i").longValue());
assertEquals(8, tuple.getLong("day_i").longValue());
@@ -1938,7 +1937,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
.add(id, "8", "text_t", "XXXX XXXX", "str_s", "c", "field_i", "60")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrParams sParams =
params(
@@ -2020,7 +2019,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
.add(id, "8", "year_i", "2014", "month_i", "4", "day_i", "2", "item_i", "1")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrParams sParams =
params(
@@ -2035,7 +2034,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
Tuple tuple;
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(2015, tuple.getLong("year_i").longValue());
assertEquals(66, tuple.getDouble("EXPR$1"), 0.0); // sum(item_i)
@@ -2055,7 +2054,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(3, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(2015, tuple.getLong("year_i").longValue());
assertEquals(11, tuple.getLong("month_i").longValue());
assertEquals(57, tuple.getDouble("EXPR$2"), 0.0); // sum(item_i)
@@ -2082,7 +2081,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(2015, tuple.getLong("year_i").longValue());
assertEquals(11, tuple.getLong("month_i").longValue());
assertEquals(8, tuple.getLong("day_i").longValue());
@@ -2133,7 +2132,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
.add(id, "8", "year_i", "2014", "month_i", "4", "day_i", "2", "item_i", "1")
.commit(cluster.getSolrClient(), COLLECTIONORALIAS);
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
SolrParams sParams =
params(
@@ -2150,7 +2149,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
Tuple tuple;
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(2015, tuple.getLong("year_i").longValue());
assertTrue(
tuple.get("year_i")
@@ -2176,7 +2175,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(3, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(2015, tuple.getLong("year_i").longValue());
assertEquals(11, tuple.getLong("month_i").longValue());
assertTrue(tuple.get("year_i") instanceof Long);
@@ -2207,7 +2206,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
assertEquals(6, tuples.size());
- tuple = tuples.get(0);
+ tuple = tuples.getFirst();
assertEquals(2015, tuple.getLong("year_i").longValue());
assertEquals(11, tuple.getLong("month_i").longValue());
assertEquals(8, tuple.getLong("day_i").longValue());
@@ -2278,13 +2277,13 @@ public class TestSQLHandler extends SolrCloudTestCase {
SolrParams sParams = params("stmt", "select id from collection1 where str_s IN ('a','b','c')");
- String baseUrl = cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ String baseUrl = cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
List<Tuple> tuples = getTuples(sParams, baseUrl);
assertEquals(3, tuples.size());
}
private String sqlUrl() {
- return cluster.getJettySolrRunners().get(0).getBaseUrl().toString();
+ return cluster.getJettySolrRunners().getFirst().getBaseUrl().toString();
}
private List<Tuple> expectResults(String sql, final int expectedCount) throws Exception {
@@ -2683,7 +2682,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
+ " FROM $ALIAS";
List<Tuple> tuples = expectResults(sql, 1);
- Tuple stats = tuples.get(0);
+ Tuple stats = tuples.getFirst();
assertEquals("A", stats.getString("min_stringx"));
assertEquals("C", stats.getString("max_stringx"));
assertEquals("aaa", stats.getString("min_textx"));
@@ -2714,7 +2713,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
private String toStatsSql(List<String> types) {
StringBuilder sb = new StringBuilder();
for (String type : types) {
- if (sb.length() > 0) {
+ if (!sb.isEmpty()) {
sb.append(", ");
}
sb.append(min("t" + type)).append(", ").append(min("p" + type));
@@ -2783,7 +2782,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
results =
expectResults(
"SELECT id FROM $ALIAS ORDER BY id ASC OFFSET " + i + " FETCH NEXT 1 ROW ONLY", 1);
- String id = results.get(0).getString("id");
+ String id = results.getFirst().getString("id");
if (id.startsWith("0")) id = id.substring(1);
assertEquals(i + 1, Integer.parseInt(id));
}
@@ -2813,7 +2812,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
expectResults(
"SELECT COUNT(1) AS total_rows, COUNT(distinct str_s) AS distinct_str, MIN(str_s) AS min_str, MAX(str_s) AS max_str FROM $ALIAS",
1);
- Tuple firstRow = tuples.get(0);
+ Tuple firstRow = tuples.getFirst();
assertEquals(maxDocs, firstRow.getLong("total_rows").longValue());
assertEquals(cardinality, firstRow.getLong("distinct_str").longValue());
@@ -2830,7 +2829,7 @@ public class TestSQLHandler extends SolrCloudTestCase {
tuples =
expectResults(
"SELECT APPROX_COUNT_DISTINCT(distinct str_s) AS approx_distinct FROM $ALIAS", 1);
- firstRow = tuples.get(0);
+ firstRow = tuples.getFirst();
assertEquals(cardinality, firstRow.getLong("approx_distinct").longValue());
tuples =
@@ -2994,13 +2993,13 @@ public class TestSQLHandler extends SolrCloudTestCase {
if (limit > 0) sql += " LIMIT " + limit;
List<Tuple> results = expectResults(sql, 1);
if (expected != null) {
- assertEquals(expected, results.get(0).get(mvField));
+ assertEquals(expected, results.getFirst().get(mvField));
} else {
- assertNull(results.get(0).get(mvField));
+ assertNull(results.getFirst().get(mvField));
}
if (expected != null) {
- String crit = "'" + expected.get(0) + "'";
+ String crit = "'" + expected.getFirst() + "'";
sql = "SELECT " + projection + " FROM $ALIAS WHERE " + mvField + "=" + crit;
if (limit > 0) sql += " LIMIT " + limit;
expectResults(sql, expCount);
diff --git a/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandlerNonCloud.java b/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandlerNonCloud.java
index 24113861fec..46072041b77 100644
--- a/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandlerNonCloud.java
+++ b/solr/modules/sql/src/test/org/apache/solr/handler/sql/TestSQLHandlerNonCloud.java
@@ -50,7 +50,7 @@ public class TestSQLHandlerNonCloud extends SolrTestCaseJ4 {
}
@Test
- public void testSQLHandler() throws Exception {
+ public void testSQLHandler() {
String sql = "select id, field_i, str_s from " + DEFAULT_TEST_COLLECTION_NAME + " limit 10";
SolrParams sParams = params("stmt", sql);
String url = solrTestRule.getBaseUrl();
(solr) 06/09: SOLR-18381: remove ClusterState.getReplicaNamesPerCollectionOnNode (#4768)
Posted by ds...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
commit fa06b3ce19f615524af656dd27b543748b0a1235
Author: Serhiy Bzhezytskyy <me...@serhiy-bzhezytskyy.com>
AuthorDate: Sat Sep 5 14:33:55 2026 +0300
SOLR-18381: remove ClusterState.getReplicaNamesPerCollectionOnNode (#4768)
Co-authored-by: Eric Pugh <ep...@opensourceconnections.com>
(cherry picked from commit 594c2a4011461e8b443ec65e685d2f320c3c2d4a)
---
.../src/java/org/apache/solr/cloud/ZkController.java | 13 +++++++++++--
.../test/org/apache/solr/cloud/ZkControllerTest.java | 9 ++++-----
.../org/apache/solr/common/cloud/ClusterState.java | 18 ------------------
3 files changed, 15 insertions(+), 25 deletions(-)
diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index 3ab82e68b1d..112b301b62c 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -2975,8 +2975,17 @@ public class ZkController implements Closeable {
log.info("Publish node={} as DOWN", nodeName);
ClusterState clusterState = getClusterState();
- Map<String, List<Replica>> replicasPerCollectionOnNode =
- clusterState.getReplicaNamesPerCollectionOnNode(nodeName);
+ Map<String, List<Replica>> replicasPerCollectionOnNode = new HashMap<>();
+ clusterState
+ .collectionStream()
+ .forEach(
+ col -> {
+ List<Replica> replicas = col.getReplicasOnNode(nodeName);
+ if (!replicas.isEmpty()) {
+ replicasPerCollectionOnNode.put(col.getName(), replicas);
+ }
+ });
+
if (distributedClusterStateUpdater.isDistributedStateUpdate()) {
// Note that with the current implementation, when distributed cluster state updates are
// enabled, we mark the node down synchronously from this thread, whereas the Overseer cluster
diff --git a/solr/core/src/test/org/apache/solr/cloud/ZkControllerTest.java b/solr/core/src/test/org/apache/solr/cloud/ZkControllerTest.java
index 1d868787d48..53c9e3fd83e 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ZkControllerTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ZkControllerTest.java
@@ -388,11 +388,10 @@ public class ZkControllerTest extends SolrCloudTestCase {
zkController.getZkStateReader().forciblyRefreshAllClusterStateSlow();
ClusterState clusterState = zkController.getClusterState();
- Map<String, List<Replica>> replicasOnNode =
- clusterState.getReplicaNamesPerCollectionOnNode(nodeName);
- assertNotNull("There should be replicas on the existing node", replicasOnNode);
- List<Replica> replicas = replicasOnNode.get(collectionName);
- assertNotNull("There should be replicas for the collection on the existing node", replicas);
+ List<Replica> replicas =
+ clusterState.getCollection(collectionName).getReplicasOnNode(nodeName);
+ assertFalse(
+ "There should be replicas for the collection on the existing node", replicas.isEmpty());
assertEquals(
"Wrong number of replicas for the collection on the existing node", 1, replicas.size());
for (Replica replica : replicas) {
diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/ClusterState.java b/solr/solrj/src/java/org/apache/solr/common/cloud/ClusterState.java
index 40832f59b9d..36c3a3f7cb8 100644
--- a/solr/solrj/src/java/org/apache/solr/common/cloud/ClusterState.java
+++ b/solr/solrj/src/java/org/apache/solr/common/cloud/ClusterState.java
@@ -23,7 +23,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
-import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
@@ -160,22 +159,6 @@ public class ClusterState implements MapWriter {
return liveNodes;
}
- @Deprecated(since = "10.0")
- public Map<String, List<Replica>> getReplicaNamesPerCollectionOnNode(final String nodeName) {
- Map<String, List<Replica>> replicaNamesPerCollectionOnNode = new HashMap<>();
- collectionStates.values().stream()
- .map(CollectionRef::get)
- .filter(Objects::nonNull)
- .forEach(
- col -> {
- List<Replica> replicas = col.getReplicasOnNode(nodeName);
- if (!replicas.isEmpty()) {
- replicaNamesPerCollectionOnNode.put(col.getName(), replicas);
- }
- });
- return replicaNamesPerCollectionOnNode;
- }
-
/** Check if node is alive. */
public boolean liveNodesContain(String name) {
return liveNodes.contains(name);
@@ -190,7 +173,6 @@ public class ClusterState implements MapWriter {
return sb.toString();
}
- @Deprecated
public static ClusterState createFromCollectionMap(
int version,
Map<String, Object> stateMap,
(solr) 09/09: SOLR-18413: Handle deleted MIGRATE routing targets (#4862)
Posted by ds...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
commit c5b2d0e375d6de9e04e2102e58f974fcc79de80c
Author: Zhenyu Li <48...@users.noreply.github.com>
AuthorDate: Sun Sep 6 00:14:34 2026 -0400
SOLR-18413: Handle deleted MIGRATE routing targets (#4862)
Problem fixed: Say the target of a MIGRATE collection is deleted, yet the source collection exists and receives an update request. The request reports failure after partially applying the mutation, leaving replicas of the source shard inconsistent. No update request is sent to the source replica. Therefore, SolrCmdDistributor does not observe a replica failure. Consequently, this request does not lower the replica's shard term or directly trigger recovery.
(cherry picked from commit 465859177153921cd0c09fb9f6c4d643d2f30d41)
---
.../fix-dangling-routing-rule-target.yml | 8 ++
.../processor/DistributedZkUpdateProcessor.java | 95 ++++++++++++----------
.../org/apache/solr/cloud/MigrateRouteKeyTest.java | 53 ++++++++++++
3 files changed, 115 insertions(+), 41 deletions(-)
diff --git a/changelog/unreleased/fix-dangling-routing-rule-target.yml b/changelog/unreleased/fix-dangling-routing-rule-target.yml
new file mode 100644
index 00000000000..a3ba20b78bc
--- /dev/null
+++ b/changelog/unreleased/fix-dangling-routing-rule-target.yml
@@ -0,0 +1,8 @@
+title: Prevent partial writes when a collection referenced by a MIGRATE routing rule has been deleted
+type: fixed
+authors:
+ - name: ZhenyuLi
+ nick: JHSUYU
+links:
+ - name: SOLR-18413
+ url: https://issues.apache.org/jira/browse/SOLR-18413
diff --git a/solr/core/src/java/org/apache/solr/update/processor/DistributedZkUpdateProcessor.java b/solr/core/src/java/org/apache/solr/update/processor/DistributedZkUpdateProcessor.java
index 49c9fc717a7..fad45bb70cb 100644
--- a/solr/core/src/java/org/apache/solr/update/processor/DistributedZkUpdateProcessor.java
+++ b/solr/core/src/java/org/apache/solr/update/processor/DistributedZkUpdateProcessor.java
@@ -1011,7 +1011,17 @@ public class DistributedZkUpdateProcessor extends DistributedUpdateProcessor {
int hash = compositeIdRouter.sliceHash(id, doc, null, coll);
for (DocRouter.Range range : ranges) {
if (range.includes(hash)) {
- DocCollection targetColl = cstate.getCollection(rule.getTargetCollectionName());
+ DocCollection targetColl =
+ cstate.getCollectionOrNull(rule.getTargetCollectionName());
+ if (targetColl == null) {
+ if (log.isInfoEnabled()) {
+ log.info(
+ "Removing shard update routing rule because the target collection {} doesn't exist",
+ rule.getTargetCollectionName());
+ }
+ removeRoutingRule(myShardId, routeKey);
+ break;
+ }
Collection<Slice> activeSlices =
targetColl.getRouter().getSearchSlicesSingle(id, null, targetColl);
if (activeSlices == null || activeSlices.isEmpty()) {
@@ -1031,46 +1041,8 @@ public class DistributedZkUpdateProcessor extends DistributedUpdateProcessor {
}
}
} else {
- ReentrantLock ruleExpiryLock = req.getCore().getRuleExpiryLock();
- if (!ruleExpiryLock.isLocked()) {
- try {
- if (ruleExpiryLock.tryLock(10, TimeUnit.MILLISECONDS)) {
- log.info("Going to expire routing rule");
- try {
- Map<String, Object> map =
- Map.of(
- Overseer.QUEUE_OPERATION,
- OverseerAction.REMOVEROUTINGRULE.toLower(),
- ZkStateReader.COLLECTION_PROP,
- collection,
- ZkStateReader.SHARD_ID_PROP,
- myShardId,
- "routeKey",
- routeKey + "!");
- if (distributedClusterStateUpdater.isDistributedStateUpdate()) {
- ZkNodeProps message = new ZkNodeProps(map);
- distributedClusterStateUpdater.doSingleStateUpdate(
- DistributedClusterStateUpdater.MutatingCommand.SliceRemoveRoutingRule,
- message,
- zkController.getOverseer().getSolrCloudManager(),
- zkController.getOverseer().getZkStateReader());
- } else {
- zkController.getOverseer().offerStateUpdate(Utils.toJSON(map));
- }
- } catch (KeeperException e) {
- log.warn(
- "Exception while removing routing rule for route key: {}", routeKey, e);
- } catch (Exception e) {
- log.error(
- "Exception while removing routing rule for route key: {}", routeKey, e);
- } finally {
- ruleExpiryLock.unlock();
- }
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
+ log.info("Removing shard update routing rule because it has expired");
+ removeRoutingRule(myShardId, routeKey);
}
}
}
@@ -1079,6 +1051,47 @@ public class DistributedZkUpdateProcessor extends DistributedUpdateProcessor {
return nodes;
}
+ private void removeRoutingRule(String shardId, String routeKey) {
+ ReentrantLock ruleExpiryLock = req.getCore().getRuleExpiryLock();
+ if (ruleExpiryLock.isLocked()) {
+ return;
+ }
+ try {
+ if (ruleExpiryLock.tryLock(10, TimeUnit.MILLISECONDS)) {
+ try {
+ Map<String, Object> map =
+ Map.of(
+ Overseer.QUEUE_OPERATION,
+ OverseerAction.REMOVEROUTINGRULE.toLower(),
+ ZkStateReader.COLLECTION_PROP,
+ collection,
+ ZkStateReader.SHARD_ID_PROP,
+ shardId,
+ "routeKey",
+ routeKey + "!");
+ if (distributedClusterStateUpdater.isDistributedStateUpdate()) {
+ ZkNodeProps message = new ZkNodeProps(map);
+ distributedClusterStateUpdater.doSingleStateUpdate(
+ DistributedClusterStateUpdater.MutatingCommand.SliceRemoveRoutingRule,
+ message,
+ zkController.getOverseer().getSolrCloudManager(),
+ zkController.getOverseer().getZkStateReader());
+ } else {
+ zkController.getOverseer().offerStateUpdate(Utils.toJSON(map));
+ }
+ } catch (KeeperException e) {
+ log.warn("Exception while removing routing rule for route key: {}", routeKey, e);
+ } catch (Exception e) {
+ log.error("Exception while removing routing rule for route key: {}", routeKey, e);
+ } finally {
+ ruleExpiryLock.unlock();
+ }
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
private void doDefensiveChecks(DistribPhase phase, UpdateCommand updateCommand) {
boolean isReplayOrPeersync =
(updateCommand.getFlags() & (UpdateCommand.REPLAY | UpdateCommand.PEER_SYNC)) != 0;
diff --git a/solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java b/solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java
index 723f9d9ba6a..09114574f2a 100644
--- a/solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/MigrateRouteKeyTest.java
@@ -100,6 +100,59 @@ public class MigrateRouteKeyTest extends SolrCloudTestCase {
assertTrue(remoteSolrException.getMessage().contains("split.key cannot be null or empty"));
}
+ @Test
+ public void updateSucceedsAfterMigrateTargetIsDeleted() throws Exception {
+ String sourceCollection = "deletedMigrateTarget-source";
+ CollectionAdminRequest.createCollection(sourceCollection, "conf", 1, 2)
+ .process(cluster.getSolrClient());
+ String targetCollection = "deletedMigrateTarget-target";
+ CollectionAdminRequest.createCollection(targetCollection, "conf", 1, 1)
+ .process(cluster.getSolrClient());
+
+ cluster.getSolrClient().add(sourceCollection, new SolrInputDocument("id", "a!1"));
+ cluster.getSolrClient().commit(sourceCollection);
+
+ invokeCollectionMigration(
+ CollectionAdminRequest.migrateData(sourceCollection, targetCollection, "a!")
+ .setForwardTimeout(45));
+ waitForState(
+ "Expected to find routing rule for split key a",
+ sourceCollection,
+ c -> {
+ if (c == null) return false;
+ Map<String, RoutingRule> routingRules = c.getSlice("shard1").getRoutingRules();
+ return routingRules != null && routingRules.containsKey("a!");
+ });
+
+ CollectionAdminRequest.deleteCollection(targetCollection).process(cluster.getSolrClient());
+ waitForState("Expected target collection deletion", targetCollection, c -> c == null);
+
+ cluster.getSolrClient().add(sourceCollection, new SolrInputDocument("id", "a!2"));
+ cluster.getSolrClient().commit(sourceCollection);
+
+ DocCollection sourceState = getCollectionState(sourceCollection);
+ assertEquals(2, sourceState.getSlice("shard1").getReplicas().size());
+ for (Replica replica : sourceState.getSlice("shard1")) {
+ try (SolrClient replicaClient = getHttpSolrClient(replica)) {
+ SolrQuery query = new SolrQuery("id:\"a!2\"");
+ query.set("distrib", false);
+ assertEquals(
+ "Document missing from replica " + replica.getName(),
+ 1,
+ replicaClient.query(query).getResults().getNumFound());
+ }
+ }
+
+ waitForState(
+ "Expected dangling routing rule removal",
+ sourceCollection,
+ c -> {
+ if (c == null) return false;
+ Map<String, RoutingRule> routingRules = c.getSlice("shard1").getRoutingRules();
+ return routingRules == null || !routingRules.containsKey("a!");
+ });
+ }
+
@Test
public void multipleShardMigrateTest() throws Exception {
(solr) 08/09: SlowCompositeReaderWrapper: fix index sort and hasBlocks (#4825)
Posted by ds...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
commit c68c3cfa33e158ccc03b4a5845ec6cce363ee303
Author: David Smiley <ds...@apache.org>
AuthorDate: Sun Sep 6 00:05:39 2026 -0400
SlowCompositeReaderWrapper: fix index sort and hasBlocks (#4825)
minor fixes; not seen in a realistic setting.
(cherry picked from commit c78158b9811417543c4f7cdf99faed8d25c5943b)
---
...owCompositeReaderWrapper-sort-hasblocks-fix.yml | 6 +++
.../solr/index/SlowCompositeReaderWrapper.java | 43 +++++++++++++---------
2 files changed, 31 insertions(+), 18 deletions(-)
diff --git a/changelog/unreleased/SlowCompositeReaderWrapper-sort-hasblocks-fix.yml b/changelog/unreleased/SlowCompositeReaderWrapper-sort-hasblocks-fix.yml
new file mode 100644
index 00000000000..c1f98f684cc
--- /dev/null
+++ b/changelog/unreleased/SlowCompositeReaderWrapper-sort-hasblocks-fix.yml
@@ -0,0 +1,6 @@
+# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
+title: SlowCompositeReaderWrapper no longer misreports the index sort and hasBlocks of a multi-segment composite view; both were previously taken from the first segment only, which is wrong whenever there is more than one segment.
+type: fixed # added, changed, fixed, deprecated, removed, dependency_update, security, other
+authors:
+ - name: David Smiley
+links:
diff --git a/solr/core/src/java/org/apache/solr/index/SlowCompositeReaderWrapper.java b/solr/core/src/java/org/apache/solr/index/SlowCompositeReaderWrapper.java
index a242ce849a3..3b20a178107 100644
--- a/solr/core/src/java/org/apache/solr/index/SlowCompositeReaderWrapper.java
+++ b/solr/core/src/java/org/apache/solr/index/SlowCompositeReaderWrapper.java
@@ -51,6 +51,7 @@ import org.apache.lucene.index.TermVectors;
import org.apache.lucene.index.Terms;
import org.apache.lucene.search.AcceptDocs;
import org.apache.lucene.search.KnnCollector;
+import org.apache.lucene.search.Sort;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.Version;
import org.apache.lucene.util.packed.PackedInts;
@@ -84,38 +85,44 @@ public final class SlowCompositeReaderWrapper extends LeafReader {
* This method is sugar for getting an {@link LeafReader} from an {@link IndexReader} of any kind.
* If the reader is already atomic, it is returned unchanged, otherwise wrapped by this class.
*/
- public static LeafReader wrap(IndexReader reader) throws IOException {
- if (reader instanceof CompositeReader) {
- return new SlowCompositeReaderWrapper((CompositeReader) reader);
+ public static LeafReader wrap(IndexReader reader) {
+ if (reader instanceof CompositeReader compositeReader) {
+ return new SlowCompositeReaderWrapper(compositeReader);
} else {
assert reader instanceof LeafReader;
return (LeafReader) reader;
}
}
- SlowCompositeReaderWrapper(CompositeReader reader) throws IOException {
+ SlowCompositeReaderWrapper(CompositeReader reader) {
in = reader;
in.registerParentReader(this);
- if (reader.leaves().isEmpty()) {
+ List<LeafReaderContext> leaves = reader.leaves();
+ if (leaves.isEmpty()) {
metaData = new LeafMetaData(Version.LATEST.major, Version.LATEST, null, false);
} else {
Version minVersion = Version.LATEST;
- for (LeafReaderContext leafReaderContext : reader.leaves()) {
- Version leafVersion = leafReaderContext.reader().getMetaData().minVersion();
- if (leafVersion == null) {
- minVersion = null;
- break;
- } else if (minVersion.onOrAfter(leafVersion)) {
- minVersion = leafVersion;
+ boolean hasBlocks = false;
+ for (LeafReaderContext leafReaderContext : leaves) {
+ LeafMetaData leafMetaData = leafReaderContext.reader().getMetaData();
+ if (minVersion != null) {
+ Version leafVersion = leafMetaData.minVersion();
+ if (leafVersion == null) {
+ minVersion = null;
+ } else if (minVersion.onOrAfter(leafVersion)) {
+ minVersion = leafVersion;
+ }
}
+ // A block (child/nested docs), once written, is never split across segments, so the
+ // composite view has blocks as soon as any one of its segments does.
+ hasBlocks |= leafMetaData.hasBlocks();
}
- LeafMetaData leafMetaData = reader.leaves().get(0).reader().getMetaData();
+ LeafMetaData firstLeafMetaData = leaves.getFirst().reader().getMetaData();
+ // The composite view is only actually sorted in the trivial single-leaf case: concatenating
+ // multiple segments does not preserve their shared per-segment sort as a sort of the whole.
+ Sort sort = leaves.size() == 1 ? firstLeafMetaData.sort() : null;
metaData =
- new LeafMetaData(
- leafMetaData.createdVersionMajor(),
- minVersion,
- leafMetaData.sort(),
- leafMetaData.hasBlocks());
+ new LeafMetaData(firstLeafMetaData.createdVersionMajor(), minVersion, sort, hasBlocks);
}
fieldInfos = FieldInfos.getMergedFieldInfos(in);
}
(solr) 04/09: SOLR-18410: CrossDC Common: multivalued params and per-entry params don't survive the serialization round-trip (#4867)
Posted by ds...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
commit 91908f28a964e06dbf0747bc4d5ab29016683359
Author: Andrzej Białecki <ab...@apache.org>
AuthorDate: Fri Sep 4 13:23:04 2026 +0200
SOLR-18410: CrossDC Common: multivalued params and per-entry params don't survive the serialization round-trip (#4867)
(cherry picked from commit 9197e3681df2c302019ec76cecec4800bb4571b5)
---
changelog/unreleased/solr-18410.yml | 8 ++
.../common/MirroredSolrRequestSerializer.java | 44 ++++++--
.../common/MirroredSolrRequestSerializerTest.java | 111 +++++++++++++++++++++
3 files changed, 157 insertions(+), 6 deletions(-)
diff --git a/changelog/unreleased/solr-18410.yml b/changelog/unreleased/solr-18410.yml
new file mode 100644
index 00000000000..7ef53e52860
--- /dev/null
+++ b/changelog/unreleased/solr-18410.yml
@@ -0,0 +1,8 @@
+title: CrossDC Common multivalued params and per-entry params don't survive the serialization round-trip
+
+type: fixed
+authors:
+ - name: Andrzej Bialecki
+links:
+ - name: SOLR-18410
+ url: https://issues.apache.org/jira/browse/SOLR-18410
diff --git a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java
index d35d1743338..4bac11fea92 100644
--- a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java
+++ b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java
@@ -29,14 +29,16 @@ import org.apache.kafka.common.serialization.Serializer;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.request.UpdateRequest;
+import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.CollectionParams;
import org.apache.solr.common.params.CoreAdminParams;
-import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.ShardParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.CollectionUtil;
import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.JavaBinCodec;
+import org.apache.solr.common.util.NamedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -84,7 +86,7 @@ public class MirroredSolrRequestSerializer
Long.parseLong(String.valueOf(requestMap.getOrDefault("submitTimeNanos", "-1")));
SolrParams params;
if (requestMap.get("params") != null) {
- params = new MapSolrParams((Map<String, String>) requestMap.get("params"));
+ params = new NamedList<>((Map<String, Object>) requestMap.get("params")).toSolrParams();
} else {
params = new ModifiableSolrParams();
}
@@ -93,7 +95,18 @@ public class MirroredSolrRequestSerializer
UpdateRequest updateRequest = (UpdateRequest) request;
List docs = (List) requestMap.get("docs");
if (docs != null) {
- updateRequest.add(docs);
+ List<Map<String, Object>> docsParams =
+ (List<Map<String, Object>>) requestMap.get("docsParams");
+ if (docsParams.size() != docs.size()) {
+ throw new RuntimeException("docs and docsParams size mismatch");
+ }
+ for (int i = 0; i < docs.size(); i++) {
+ Map<String, Object> docParams = docsParams.get(i);
+ updateRequest.add(
+ (SolrInputDocument) docs.get(i),
+ docParams == null ? null : (Integer) docParams.get(UpdateRequest.COMMIT_WITHIN),
+ docParams == null ? null : (Boolean) docParams.get(UpdateRequest.OVERWRITE));
+ }
} else {
updateRequest.add("id", "1"); // TODO huh?
updateRequest.getDocumentsMap().clear();
@@ -101,7 +114,18 @@ public class MirroredSolrRequestSerializer
List<String> deletes = (List<String>) requestMap.get("deletes");
if (deletes != null) {
- updateRequest.deleteById(deletes);
+ List<Map<String, Object>> deletesParams =
+ (List<Map<String, Object>>) requestMap.get("deletesParams");
+ if (deletesParams.size() != deletes.size()) {
+ throw new RuntimeException("deletes and deletesParams size mismatch");
+ }
+ for (int i = 0; i < deletes.size(); i++) {
+ Map<String, Object> deleteParams = deletesParams.get(i);
+ updateRequest.deleteById(
+ deletes.get(i),
+ deleteParams == null ? null : (String) deleteParams.get(ShardParams._ROUTE_),
+ deleteParams == null ? null : (Long) deleteParams.get(UpdateRequest.VER));
+ }
}
List<String> deletesQuery = (List<String>) requestMap.get("deleteQuery");
@@ -185,8 +209,16 @@ public class MirroredSolrRequestSerializer
map.put("params", solrRequest.getParams());
map.put("type", request.getType().toString());
if (solrRequest instanceof UpdateRequest update) {
- map.put("docs", update.getDocuments());
- map.put("deletes", update.getDeleteById());
+ Map<SolrInputDocument, Map<String, Object>> docsMap = update.getDocumentsMap();
+ if (docsMap != null && !docsMap.isEmpty()) {
+ map.put("docs", docsMap.keySet());
+ map.put("docsParams", update.getDocumentsMap().values());
+ }
+ Map<String, Map<String, Object>> deletes = update.getDeleteByIdMap();
+ if (deletes != null && !deletes.isEmpty()) {
+ map.put("deletes", deletes.keySet());
+ map.put("deletesParams", deletes.values());
+ }
map.put("deleteQuery", update.getDeleteQuery());
} else if (solrRequest instanceof MirroredSolrRequest.MirroredConfigSetRequest config) {
map.put("method", config.getMethod().toString());
diff --git a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/common/MirroredSolrRequestSerializerTest.java b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/common/MirroredSolrRequestSerializerTest.java
index e0afa71cd33..0b211b8bc32 100644
--- a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/common/MirroredSolrRequestSerializerTest.java
+++ b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/common/MirroredSolrRequestSerializerTest.java
@@ -17,10 +17,15 @@
package org.apache.solr.crossdc.common;
import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
import org.apache.lucene.tests.util.TestUtil;
import org.apache.solr.SolrTestCase;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.ShardParams;
+import org.apache.solr.common.params.SolrParams;
import org.junit.Test;
public class MirroredSolrRequestSerializerTest extends SolrTestCase {
@@ -55,4 +60,110 @@ public class MirroredSolrRequestSerializerTest extends SolrTestCase {
assertEquals(fieldValue, deserValue);
}
}
+
+ @Test
+ public void testMultivaluedParamsRoundTrip() {
+ MirroredSolrRequestSerializer serializer = new MirroredSolrRequestSerializer();
+ UpdateRequest req = new UpdateRequest();
+ SolrInputDocument doc = new SolrInputDocument();
+ doc.setField("id", "1");
+ req.add(doc);
+
+ ModifiableSolrParams params = new ModifiableSolrParams();
+ params.set("q", "single-value");
+ params.add("fq", "a", "b", "c");
+ req.setParams(params);
+
+ MirroredSolrRequest<?> mirroredRequest = new MirroredSolrRequest<>(req);
+ byte[] data = serializer.serialize("test", mirroredRequest);
+ MirroredSolrRequest<?> deserialized = serializer.deserialize("test", data);
+
+ SolrParams deserializedParams = deserialized.getSolrRequest().getParams();
+ assertEquals("single-value", deserializedParams.get("q"));
+ assertArrayEquals(new String[] {"a", "b", "c"}, deserializedParams.getParams("fq"));
+ }
+
+ @Test
+ public void testDocsParamsRoundTrip() {
+ MirroredSolrRequestSerializer serializer = new MirroredSolrRequestSerializer();
+ UpdateRequest req = new UpdateRequest();
+ SolrInputDocument doc1 = new SolrInputDocument();
+ doc1.setField("id", "1");
+ SolrInputDocument doc2 = new SolrInputDocument();
+ doc2.setField("id", "2");
+ req.add(doc1, 5000, true);
+ req.add(doc2, 1000, false);
+
+ MirroredSolrRequest<?> mirroredRequest = new MirroredSolrRequest<>(req);
+ byte[] data = serializer.serialize("test", mirroredRequest);
+ MirroredSolrRequest<?> deserialized = serializer.deserialize("test", data);
+
+ UpdateRequest deserializedReq = (UpdateRequest) deserialized.getSolrRequest();
+ Map<SolrInputDocument, Map<String, Object>> docsMap = deserializedReq.getDocumentsMap();
+ assertEquals(2, docsMap.size());
+ for (Map.Entry<SolrInputDocument, Map<String, Object>> entry : docsMap.entrySet()) {
+ String id = (String) entry.getKey().getFieldValue("id");
+ Map<String, Object> docParams = entry.getValue();
+ if ("1".equals(id)) {
+ assertEquals(5000, docParams.get(UpdateRequest.COMMIT_WITHIN));
+ assertEquals(Boolean.TRUE, docParams.get(UpdateRequest.OVERWRITE));
+ } else if ("2".equals(id)) {
+ assertEquals(1000, docParams.get(UpdateRequest.COMMIT_WITHIN));
+ assertEquals(Boolean.FALSE, docParams.get(UpdateRequest.OVERWRITE));
+ } else {
+ fail("Unexpected document id: " + id);
+ }
+ }
+ }
+
+ @Test
+ public void testDeletesParamsRoundTrip() {
+ MirroredSolrRequestSerializer serializer = new MirroredSolrRequestSerializer();
+ UpdateRequest req = new UpdateRequest();
+ req.deleteById("1", "shard1", 100L);
+ req.deleteById("2", "shard2", 200L);
+
+ MirroredSolrRequest<?> mirroredRequest = new MirroredSolrRequest<>(req);
+ byte[] data = serializer.serialize("test", mirroredRequest);
+ MirroredSolrRequest<?> deserialized = serializer.deserialize("test", data);
+
+ UpdateRequest deserializedReq = (UpdateRequest) deserialized.getSolrRequest();
+ Map<String, Map<String, Object>> deleteByIdMap = deserializedReq.getDeleteByIdMap();
+ assertEquals(2, deleteByIdMap.size());
+ Map<String, Object> params1 = deleteByIdMap.get("1");
+ assertEquals("shard1", params1.get(ShardParams._ROUTE_));
+ assertEquals(100L, params1.get(UpdateRequest.VER));
+ Map<String, Object> params2 = deleteByIdMap.get("2");
+ assertEquals("shard2", params2.get(ShardParams._ROUTE_));
+ assertEquals(200L, params2.get(UpdateRequest.VER));
+ }
+
+ @Test
+ public void testBothDocsAndDeletesParamsRoundTrip() {
+ MirroredSolrRequestSerializer serializer = new MirroredSolrRequestSerializer();
+ UpdateRequest req = new UpdateRequest();
+ SolrInputDocument doc = new SolrInputDocument();
+ doc.setField("id", "1");
+ req.add(doc, 2000, true);
+ req.deleteById("2", "shard1", 50L);
+ req.deleteByQuery("field:value");
+
+ MirroredSolrRequest<?> mirroredRequest = new MirroredSolrRequest<>(req);
+ byte[] data = serializer.serialize("test", mirroredRequest);
+ MirroredSolrRequest<?> deserialized = serializer.deserialize("test", data);
+
+ UpdateRequest deserializedReq = (UpdateRequest) deserialized.getSolrRequest();
+ Map<SolrInputDocument, Map<String, Object>> docsMap = deserializedReq.getDocumentsMap();
+ assertEquals(1, docsMap.size());
+ Map<String, Object> docParams = docsMap.values().iterator().next();
+ assertEquals(2000, docParams.get(UpdateRequest.COMMIT_WITHIN));
+ assertEquals(Boolean.TRUE, docParams.get(UpdateRequest.OVERWRITE));
+
+ Map<String, Map<String, Object>> deleteByIdMap = deserializedReq.getDeleteByIdMap();
+ Map<String, Object> deleteParams = deleteByIdMap.get("2");
+ assertEquals("shard1", deleteParams.get(ShardParams._ROUTE_));
+ assertEquals(50L, deleteParams.get(UpdateRequest.VER));
+
+ assertEquals(List.of("field:value"), deserializedReq.getDeleteQuery());
+ }
}
(solr) 05/09: SOLR-16390: Move cluster command-status APIs to /cluster/commands (#4866)
Posted by ds...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
commit cba01ab8d3b3d797ef41ce030b8458ec50aca1ab
Author: Prithvi S <pr...@gmail.com>
AuthorDate: Fri Sep 4 22:52:56 2026 +0530
SOLR-16390: Move cluster command-status APIs to /cluster/commands (#4866)
Signed-off-by: prithvi <pr...@gmail.com>
Co-authored-by: Eric Pugh <ep...@opensourceconnections.com>
(cherry picked from commit 6699861b7f53f744d08cadb39af4ef841158d05f)
---
.../SOLR-16390-cluster-command-status.yml | 8 +
dev-docs/v2-api-conventions.adoc | 2 +
.../client/api/endpoint/ClusterCommandsApi.java | 71 ++++++
.../api/endpoint/GetNodeCommandStatusApi.java | 2 +-
.../model/DeleteClusterCommandStatusResponse.java | 31 +++
.../api/model/GetClusterCommandStatusResponse.java | 97 +++++++
.../java/org/apache/solr/handler/ClusterAPI.java | 24 --
.../solr/handler/admin/CollectionsHandler.java | 164 +-----------
.../solr/handler/admin/api/ClusterCommands.java | 278 +++++++++++++++++++++
.../solr/handler/V2ClusterAPIMappingTest.java | 19 --
.../handler/admin/api/ClusterCommandsTest.java | 128 ++++++++++
.../configuration-guide/pages/collections-api.adoc | 9 +-
12 files changed, 628 insertions(+), 205 deletions(-)
diff --git a/changelog/unreleased/SOLR-16390-cluster-command-status.yml b/changelog/unreleased/SOLR-16390-cluster-command-status.yml
new file mode 100644
index 00000000000..775b6364937
--- /dev/null
+++ b/changelog/unreleased/SOLR-16390-cluster-command-status.yml
@@ -0,0 +1,8 @@
+title: "v2 cluster command-status APIs are now more REST-ful at GET/DELETE /api/cluster/commands/{id} and DELETE /api/cluster/commands (the previous /api/cluster/command-status paths are gone). SolrJ provides ClusterApi.GetClusterCommandStatus, ClusterApi.DeleteClusterCommandStatus, and ClusterApi.DeleteAllClusterCommandStatuses."
+type: changed
+authors:
+ - name: Prithvi S
+ nick: iprithv
+links:
+ - name: SOLR-16390
+ url: https://issues.apache.org/jira/browse/SOLR-16390
diff --git a/dev-docs/v2-api-conventions.adoc b/dev-docs/v2-api-conventions.adoc
index d40d7dd4a48..08a9d01fdba 100644
--- a/dev-docs/v2-api-conventions.adoc
+++ b/dev-docs/v2-api-conventions.adoc
@@ -23,6 +23,8 @@ Following these guidelines has given us the following (non-exhaustive) list of v
* `/api/backups/specificBackupName/versions/specificVersion`
* `/api/cluster/nodes/specificNodeName/roles`
* `/api/cluster/nodes/specificNodeName/roles/specificRoleName`
+* `/api/cluster/commands`
+* `/api/cluster/commands/specificCommandId`
* `/api/cluster/properties`
* `/api/cluster/properties/specificPropertyName`
* `/api/collections`
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterCommandsApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterCommandsApi.java
new file mode 100644
index 00000000000..bfd3c19eda8
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterCommandsApi.java
@@ -0,0 +1,71 @@
+/*
+ * 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 io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import org.apache.solr.client.api.model.DeleteClusterCommandStatusResponse;
+import org.apache.solr.client.api.model.GetClusterCommandStatusResponse;
+
+/**
+ * V2 API definitions for cluster-level asynchronous Collection API command status.
+ *
+ * <p>These APIs are analogous to the v1 {@code /admin/collections?action=REQUESTSTATUS} and {@code
+ * /admin/collections?action=DELETESTATUS} commands. They are not to be confused with the node-local
+ * {@link GetNodeCommandStatusApi} under {@code /api/node/commands}.
+ */
+@Path("/cluster/commands")
+public interface ClusterCommandsApi {
+
+ @GET
+ @Path("/{requestId}")
+ @Operation(
+ summary = "Request the status of an already submitted asynchronous Collection API call.",
+ tags = {"cluster"})
+ GetClusterCommandStatusResponse getClusterCommandStatus(
+ @Parameter(
+ description = "The user defined request-id for the asynchronous request.",
+ required = true)
+ @PathParam("requestId")
+ String requestId)
+ throws Exception;
+
+ @DELETE
+ @Path("/{requestId}")
+ @Operation(
+ summary =
+ "Delete the stored status of a completed or failed asynchronous Collection API call.",
+ tags = {"cluster"})
+ DeleteClusterCommandStatusResponse deleteClusterCommandStatus(
+ @Parameter(
+ description = "The user defined request-id whose stored response should be cleared.",
+ required = true)
+ @PathParam("requestId")
+ String requestId)
+ throws Exception;
+
+ @DELETE
+ @Operation(
+ summary =
+ "Delete the stored status of all completed and failed asynchronous Collection API calls.",
+ tags = {"cluster"})
+ DeleteClusterCommandStatusResponse deleteAllClusterCommandStatuses() throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/GetNodeCommandStatusApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/GetNodeCommandStatusApi.java
index a2b5bcc5986..cc807074d59 100644
--- a/solr/api/src/java/org/apache/solr/client/api/endpoint/GetNodeCommandStatusApi.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/GetNodeCommandStatusApi.java
@@ -28,7 +28,7 @@ import org.apache.solr.client.api.model.GetNodeCommandStatusResponse;
*
* <p>This API is analogous to the v1 /admin/cores?action=REQUESTSTATUS command. It is not to be
* confused with the more robust asynchronous command support offered under the v2
- * `/cluster/command-status` path (or the corresponding v1 path
+ * `/cluster/commands` path (or the corresponding v1 path
* `/solr/admin/collections?action=REQUESTSTATUS`). Async support at the core level differs in that
* command IDs are local to individual Solr nodes and are not persisted across restarts.
*
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteClusterCommandStatusResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/DeleteClusterCommandStatusResponse.java
new file mode 100644
index 00000000000..4571ec19f26
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/DeleteClusterCommandStatusResponse.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Response body for {@code DELETE /api/cluster/commands} and {@code DELETE
+ * /api/cluster/commands/{requestId}}.
+ */
+public class DeleteClusterCommandStatusResponse extends SolrJerseyResponse {
+
+ @JsonProperty("status")
+ @Schema(description = "A message describing the result of the delete.")
+ public String status;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/GetClusterCommandStatusResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/GetClusterCommandStatusResponse.java
new file mode 100644
index 00000000000..6b96427cacd
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/GetClusterCommandStatusResponse.java
@@ -0,0 +1,97 @@
+/*
+ * 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.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonValue;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.solr.client.api.util.ReflectWritable;
+
+/**
+ * Response body for {@code GET /api/cluster/commands/{requestId}}.
+ *
+ * <p>Completed and failed commands also flatten the original command response (for example {@code
+ * success} / {@code failure} sub-responses) into this object as additional properties.
+ */
+public class GetClusterCommandStatusResponse extends SolrJerseyResponse {
+
+ @JsonProperty("status")
+ @Schema(description = "The current state of the asynchronous request and a descriptive message.")
+ public CommandStatus status;
+
+ private Map<String, Object> unknownFields = new HashMap<>();
+
+ @JsonAnyGetter
+ public Map<String, Object> unknownProperties() {
+ return unknownFields;
+ }
+
+ @JsonAnySetter
+ public void setUnknownProperty(String field, Object value) {
+ unknownFields.put(field, value);
+ }
+
+ /** Nested {@code status} object returned by REQUESTSTATUS. */
+ public static class CommandStatus implements ReflectWritable {
+ @JsonProperty("state")
+ @Schema(description = "Request state: submitted, running, completed, failed, or notfound.")
+ public State state;
+
+ @JsonProperty("msg")
+ @Schema(description = "A message describing where the request was found, if at all.")
+ public String msg;
+
+ /**
+ * The state of an asynchronous request. Mirrors {@code
+ * org.apache.solr.client.solrj.response.RequestStatusState}'s constants and wire keys; kept as
+ * a separate type here since this module (solr:api) cannot depend on solrj.
+ */
+ public enum State {
+ SUBMITTED("submitted"),
+ RUNNING("running"),
+ COMPLETED("completed"),
+ FAILED("failed"),
+ NOT_FOUND("notfound");
+
+ private final String key;
+
+ State(String key) {
+ this.key = key;
+ }
+
+ @JsonValue
+ public String getKey() {
+ return key;
+ }
+
+ @JsonCreator
+ public static State fromKey(String key) {
+ for (State state : values()) {
+ if (state.key.equalsIgnoreCase(key)) {
+ return state;
+ }
+ }
+ throw new IllegalArgumentException("Unknown request status state: " + key);
+ }
+ }
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 36ee024f7f2..4e672348292 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -17,16 +17,11 @@
package org.apache.solr.handler;
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
-import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
-import static org.apache.solr.common.params.CollectionParams.ACTION;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDROLE;
-import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESTATUS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.OVERSEERSTATUS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.REMOVEROLE;
-import static org.apache.solr.common.params.CollectionParams.CollectionAction.REQUESTSTATUS;
import static org.apache.solr.core.RateLimiterConfig.RL_CONFIG_KEY;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
@@ -199,18 +194,6 @@ public class ClusterAPI {
collectionsHandler.handleRequestBody(wrapParams(req, "action", OVERSEERSTATUS.lowerName), rsp);
}
- @EndPoint(method = DELETE, path = "/cluster/command-status/{id}", permission = COLL_EDIT_PERM)
- public void deleteCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- final Map<String, Object> v1Params =
- Map.of(ACTION, DELETESTATUS.lowerName, REQUESTID, req.getPathTemplateValues().get("id"));
- collectionsHandler.handleRequestBody(wrapParams(req, v1Params), rsp);
- }
-
- @EndPoint(method = DELETE, path = "/cluster/command-status", permission = COLL_EDIT_PERM)
- public void flushCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- CollectionsHandler.CollectionOperation.DELETESTATUS_OP.execute(req, rsp, collectionsHandler);
- }
-
public static SolrQueryRequest wrapParams(SolrQueryRequest req, Object... def) {
Map<String, Object> m = Utils.makeMap(def);
return wrapParams(req, m);
@@ -232,13 +215,6 @@ public class ClusterAPI {
return req;
}
- @EndPoint(method = GET, path = "/cluster/command-status/{id}", permission = COLL_READ_PERM)
- public void getCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- final Map<String, Object> v1Params =
- Map.of(ACTION, REQUESTSTATUS.lowerName, REQUESTID, req.getPathTemplateValues().get("id"));
- collectionsHandler.handleRequestBody(wrapParams(req, v1Params), rsp);
- }
-
@EndPoint(method = GET, path = "/cluster/nodes", permission = COLL_READ_PERM)
public void getNodes(SolrQueryRequest req, SolrQueryResponse rsp) {
rsp.add("nodes", getCoreContainer().getZkController().getClusterState().getLiveNodes());
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 6e3c9e453d7..f901fffc6ab 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -16,16 +16,10 @@
*/
package org.apache.solr.handler.admin;
-import static org.apache.solr.client.solrj.response.RequestStatusState.COMPLETED;
-import static org.apache.solr.client.solrj.response.RequestStatusState.FAILED;
-import static org.apache.solr.client.solrj.response.RequestStatusState.NOT_FOUND;
-import static org.apache.solr.client.solrj.response.RequestStatusState.RUNNING;
-import static org.apache.solr.client.solrj.response.RequestStatusState.SUBMITTED;
import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.CREATE_NODE_SET;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.CREATE_NODE_SET_SHUFFLE;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.NUM_SLICES;
-import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.SHARD_UNIQUE;
import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
@@ -133,10 +127,8 @@ import org.apache.solr.client.api.model.UpdateAliasPropertiesRequestBody;
import org.apache.solr.client.api.model.UpdateCollectionPropertyRequestBody;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.response.RequestStatusState;
import org.apache.solr.cloud.OverseerSolrResponse;
import org.apache.solr.cloud.OverseerSolrResponseSerializer;
-import org.apache.solr.cloud.OverseerTaskQueue;
import org.apache.solr.cloud.OverseerTaskQueue.QueueEvent;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.cloud.ZkController.NotInClusterStateException;
@@ -162,8 +154,6 @@ import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.RequiredSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.Pair;
-import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CloudConfig;
@@ -176,6 +166,7 @@ import org.apache.solr.handler.admin.api.AdminAPIBase;
import org.apache.solr.handler.admin.api.AliasProperty;
import org.apache.solr.handler.admin.api.BalanceReplicas;
import org.apache.solr.handler.admin.api.BalanceShardUnique;
+import org.apache.solr.handler.admin.api.ClusterCommands;
import org.apache.solr.handler.admin.api.ClusterProperty;
import org.apache.solr.handler.admin.api.CollectionProperty;
import org.apache.solr.handler.admin.api.CollectionStatus;
@@ -437,13 +428,6 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
return submitCollectionApiCommand(coreContainer.getZkController(), adminCmdContext, m, timeout);
}
- private boolean overseerCollectionQueueContains(String asyncId)
- throws KeeperException, InterruptedException {
- OverseerTaskQueue collectionQueue =
- coreContainer.getZkController().getOverseerCollectionQueue();
- return collectionQueue.containsTaskWithRequestId(ASYNC, asyncId);
- }
-
/**
* Copy prefixed params into a map. There must only be one value for these parameters.
*
@@ -490,14 +474,6 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
return Category.ADMIN;
}
- private static void addStatusToResponse(
- NamedList<Object> results, RequestStatusState state, String msg) {
- SimpleOrderedMap<String> status = new SimpleOrderedMap<>();
- status.add("state", state.getKey());
- status.add("msg", msg);
- results.add("status", status);
- }
-
@SuppressWarnings("ImmutableEnumChecker")
public enum CollectionOperation implements CollectionOp {
CREATE_OP(
@@ -789,141 +765,14 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
REQUESTSTATUS_OP(
REQUESTSTATUS,
(req, rsp, h) -> {
- req.getParams().required().check(REQUESTID);
-
- final CoreContainer coreContainer = h.coreContainer;
- final String requestId = req.getParams().get(REQUESTID);
- final ZkController zkController = coreContainer.getZkController();
-
- final NamedList<Object> status = new NamedList<>();
- if (zkController.getDistributedCommandRunner().isEmpty()) {
- if (zkController.getOverseerRunningMap().contains(requestId)) {
- addStatusToResponse(status, RUNNING, "found [" + requestId + "] in running tasks");
- } else if (zkController.getOverseerCompletedMap().contains(requestId)) {
- final byte[] mapEntry = zkController.getOverseerCompletedMap().get(requestId);
- rsp.getValues()
- .addAll(OverseerSolrResponseSerializer.deserialize(mapEntry).getResponse());
- addStatusToResponse(
- status, COMPLETED, "found [" + requestId + "] in completed tasks");
- } else if (zkController.getOverseerFailureMap().contains(requestId)) {
- final byte[] mapEntry = zkController.getOverseerFailureMap().get(requestId);
- rsp.getValues()
- .addAll(OverseerSolrResponseSerializer.deserialize(mapEntry).getResponse());
- addStatusToResponse(status, FAILED, "found [" + requestId + "] in failed tasks");
- } else if (h.overseerCollectionQueueContains(requestId)) {
- addStatusToResponse(
- status, SUBMITTED, "found [" + requestId + "] in submitted tasks");
- } else {
- addStatusToResponse(
- status, NOT_FOUND, "Did not find [" + requestId + "] in any tasks queue");
- }
- } else {
- Pair<RequestStatusState, OverseerSolrResponse> sr =
- zkController
- .getDistributedCommandRunner()
- .get()
- .getAsyncTaskRequestStatus(requestId);
- final String message;
- switch (sr.first()) {
- case COMPLETED:
- message = "found [" + requestId + "] in completed tasks";
- rsp.getValues().addAll(sr.second().getResponse());
- break;
- case FAILED:
- message = "found [" + requestId + "] in failed tasks";
- rsp.getValues().addAll(sr.second().getResponse());
- break;
- case RUNNING:
- message = "found [" + requestId + "] in running tasks";
- break;
- case SUBMITTED:
- message = "found [" + requestId + "] in submitted tasks";
- break;
- default:
- message = "Did not find [" + requestId + "] in any tasks queue";
- }
- addStatusToResponse(status, sr.first(), message);
- }
-
- rsp.getValues().addAll(status);
+ ClusterCommands.invokeGetFromV1Params(h.coreContainer, req, rsp);
return null;
}),
DELETESTATUS_OP(
DELETESTATUS,
- new CollectionOp() {
- @Override
- public Map<String, Object> execute(
- SolrQueryRequest req, SolrQueryResponse rsp, CollectionsHandler h) throws Exception {
- final CoreContainer coreContainer = h.coreContainer;
- final String requestId = req.getParams().get(REQUESTID);
- final ZkController zkController = coreContainer.getZkController();
- boolean flush = req.getParams().getBool(CollectionAdminParams.FLUSH, false);
-
- if (requestId == null && !flush) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST, "Either requestid or flush parameter must be specified.");
- }
-
- if (requestId != null && flush) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST,
- "Both requestid and flush parameters can not be specified together.");
- }
-
- if (zkController.getDistributedCommandRunner().isEmpty()) {
- if (flush) {
- Collection<String> completed = zkController.getOverseerCompletedMap().keys();
- Collection<String> failed = zkController.getOverseerFailureMap().keys();
- for (String asyncId : completed) {
- zkController.getOverseerCompletedMap().remove(asyncId);
- zkController.clearAsyncId(asyncId);
- }
- for (String asyncId : failed) {
- zkController.getOverseerFailureMap().remove(asyncId);
- zkController.clearAsyncId(asyncId);
- }
- rsp.getValues()
- .add("status", "successfully cleared stored collection api responses");
- } else {
- // Request to cleanup
- if (zkController.getOverseerCompletedMap().remove(requestId)) {
- zkController.clearAsyncId(requestId);
- rsp.getValues()
- .add(
- "status", "successfully removed stored response for [" + requestId + "]");
- } else if (zkController.getOverseerFailureMap().remove(requestId)) {
- zkController.clearAsyncId(requestId);
- rsp.getValues()
- .add(
- "status", "successfully removed stored response for [" + requestId + "]");
- } else {
- rsp.getValues()
- .add("status", "[" + requestId + "] not found in stored responses");
- // Don't call zkController.clearAsyncId for this, since it could be a
- // running/pending task
- }
- }
- } else {
- if (flush) {
- zkController.getDistributedCommandRunner().get().deleteAllAsyncIds();
- rsp.getValues()
- .add("status", "successfully cleared stored collection api responses");
- } else {
- if (zkController
- .getDistributedCommandRunner()
- .get()
- .deleteSingleAsyncId(requestId)) {
- rsp.getValues()
- .add(
- "status", "successfully removed stored response for [" + requestId + "]");
- } else {
- rsp.getValues()
- .add("status", "[" + requestId + "] not found in stored responses");
- }
- }
- }
- return null;
- }
+ (req, rsp, h) -> {
+ ClusterCommands.invokeDeleteFromV1Params(h.coreContainer, req, rsp);
+ return null;
}),
ADDREPLICA_OP(
ADDREPLICA,
@@ -1380,7 +1229,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
ListCollectionSnapshots.class,
CreateCollectionSnapshot.class,
DeleteCollectionSnapshot.class,
- ClusterProperty.class);
+ ClusterProperty.class,
+ ClusterCommands.class);
}
@Override
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterCommands.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterCommands.java
new file mode 100644
index 00000000000..792642dbe6c
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterCommands.java
@@ -0,0 +1,278 @@
+/*
+ * 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 static org.apache.solr.client.solrj.response.RequestStatusState.COMPLETED;
+import static org.apache.solr.client.solrj.response.RequestStatusState.FAILED;
+import static org.apache.solr.client.solrj.response.RequestStatusState.NOT_FOUND;
+import static org.apache.solr.client.solrj.response.RequestStatusState.RUNNING;
+import static org.apache.solr.client.solrj.response.RequestStatusState.SUBMITTED;
+import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
+import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
+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.util.Collection;
+import java.util.Map;
+import org.apache.solr.client.api.endpoint.ClusterCommandsApi;
+import org.apache.solr.client.api.model.DeleteClusterCommandStatusResponse;
+import org.apache.solr.client.api.model.GetClusterCommandStatusResponse;
+import org.apache.solr.client.api.model.GetClusterCommandStatusResponse.CommandStatus;
+import org.apache.solr.client.solrj.response.RequestStatusState;
+import org.apache.solr.cloud.OverseerSolrResponse;
+import org.apache.solr.cloud.OverseerSolrResponseSerializer;
+import org.apache.solr.cloud.ZkController;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.params.CollectionAdminParams;
+import org.apache.solr.common.params.CoreAdminParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.Pair;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.handler.api.V2ApiUtils;
+import org.apache.solr.jersey.PermissionName;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+/**
+ * V2 APIs for checking and deleting cluster-level asynchronous Collection API command status.
+ *
+ * <p>{@code GET /api/cluster/commands/{requestId}} is analogous to v1 {@code
+ * /admin/collections?action=REQUESTSTATUS}. {@code DELETE /api/cluster/commands/{requestId}} and
+ * {@code DELETE /api/cluster/commands} are analogous to v1 {@code
+ * /admin/collections?action=DELETESTATUS}.
+ *
+ * <p>The v1 CollectionsHandler operations delegate to this class.
+ */
+public class ClusterCommands extends AdminAPIBase implements ClusterCommandsApi {
+
+ @Inject
+ public ClusterCommands(
+ CoreContainer coreContainer,
+ SolrQueryRequest solrQueryRequest,
+ SolrQueryResponse solrQueryResponse) {
+ super(coreContainer, solrQueryRequest, solrQueryResponse);
+ }
+
+ @Override
+ @PermissionName(COLL_READ_PERM)
+ public GetClusterCommandStatusResponse getClusterCommandStatus(String requestId)
+ throws Exception {
+ ensureRequiredParameterProvided(CoreAdminParams.REQUESTID, requestId);
+ fetchAndValidateZooKeeperAwareCoreContainer();
+
+ final GetClusterCommandStatusResponse response =
+ instantiateJerseyResponse(GetClusterCommandStatusResponse.class);
+ final ZkController zkController = coreContainer.getZkController();
+
+ if (zkController.getDistributedCommandRunner().isEmpty()) {
+ if (zkController.getOverseerRunningMap().contains(requestId)) {
+ setStatus(response, RUNNING, requestId);
+ } else if (zkController.getOverseerCompletedMap().contains(requestId)) {
+ copyStoredCommandResponse(
+ response,
+ OverseerSolrResponseSerializer.deserialize(
+ zkController.getOverseerCompletedMap().get(requestId))
+ .getResponse());
+ setStatus(response, COMPLETED, requestId);
+ } else if (zkController.getOverseerFailureMap().contains(requestId)) {
+ copyStoredCommandResponse(
+ response,
+ OverseerSolrResponseSerializer.deserialize(
+ zkController.getOverseerFailureMap().get(requestId))
+ .getResponse());
+ setStatus(response, FAILED, requestId);
+ } else if (overseerCollectionQueueContains(requestId)) {
+ setStatus(response, SUBMITTED, requestId);
+ } else {
+ setStatus(response, NOT_FOUND, requestId);
+ }
+ } else {
+ Pair<RequestStatusState, OverseerSolrResponse> sr =
+ zkController.getDistributedCommandRunner().get().getAsyncTaskRequestStatus(requestId);
+ switch (sr.first()) {
+ case COMPLETED:
+ case FAILED:
+ copyStoredCommandResponse(response, sr.second().getResponse());
+ break;
+ default:
+ break;
+ }
+ setStatus(response, sr.first(), requestId);
+ }
+
+ return response;
+ }
+
+ @Override
+ @PermissionName(COLL_EDIT_PERM)
+ public DeleteClusterCommandStatusResponse deleteClusterCommandStatus(String requestId)
+ throws Exception {
+ ensureRequiredParameterProvided(CoreAdminParams.REQUESTID, requestId);
+ fetchAndValidateZooKeeperAwareCoreContainer();
+
+ final DeleteClusterCommandStatusResponse response =
+ instantiateJerseyResponse(DeleteClusterCommandStatusResponse.class);
+ final ZkController zkController = coreContainer.getZkController();
+
+ if (zkController.getDistributedCommandRunner().isEmpty()) {
+ if (zkController.getOverseerCompletedMap().remove(requestId)) {
+ zkController.clearAsyncId(requestId);
+ response.status = "successfully removed stored response for [" + requestId + "]";
+ } else if (zkController.getOverseerFailureMap().remove(requestId)) {
+ zkController.clearAsyncId(requestId);
+ response.status = "successfully removed stored response for [" + requestId + "]";
+ } else {
+ // Don't call zkController.clearAsyncId for this, since it could be a running/pending task
+ response.status = "[" + requestId + "] not found in stored responses";
+ }
+ } else if (zkController.getDistributedCommandRunner().get().deleteSingleAsyncId(requestId)) {
+ response.status = "successfully removed stored response for [" + requestId + "]";
+ } else {
+ response.status = "[" + requestId + "] not found in stored responses";
+ }
+
+ return response;
+ }
+
+ @Override
+ @PermissionName(COLL_EDIT_PERM)
+ public DeleteClusterCommandStatusResponse deleteAllClusterCommandStatuses() throws Exception {
+ fetchAndValidateZooKeeperAwareCoreContainer();
+
+ final DeleteClusterCommandStatusResponse response =
+ instantiateJerseyResponse(DeleteClusterCommandStatusResponse.class);
+ final ZkController zkController = coreContainer.getZkController();
+
+ if (zkController.getDistributedCommandRunner().isEmpty()) {
+ Collection<String> completed = zkController.getOverseerCompletedMap().keys();
+ Collection<String> failed = zkController.getOverseerFailureMap().keys();
+ for (String asyncId : completed) {
+ zkController.getOverseerCompletedMap().remove(asyncId);
+ zkController.clearAsyncId(asyncId);
+ }
+ for (String asyncId : failed) {
+ zkController.getOverseerFailureMap().remove(asyncId);
+ zkController.clearAsyncId(asyncId);
+ }
+ } else {
+ zkController.getDistributedCommandRunner().get().deleteAllAsyncIds();
+ }
+ response.status = "successfully cleared stored collection api responses";
+ return response;
+ }
+
+ /**
+ * v1 {@code REQUESTSTATUS} entrypoint. Squashes the JAX-RS response into {@code rsp}, converting
+ * the nested status object to a {@link SimpleOrderedMap} so SolrJ's v1 {@code RequestStatus}
+ * parser continues to work.
+ */
+ public static void invokeGetFromV1Params(
+ CoreContainer coreContainer, SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+ req.getParams().required().check(REQUESTID);
+ final ClusterCommands api = new ClusterCommands(coreContainer, req, rsp);
+ final GetClusterCommandStatusResponse jerseyResponse =
+ api.getClusterCommandStatus(req.getParams().get(REQUESTID));
+ V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, jerseyResponse);
+ convertStatusToNamedList(rsp);
+ }
+
+ /**
+ * v1 {@code DELETESTATUS} entrypoint. {@code requestid} and {@code flush} remain mutually
+ * exclusive query parameters on v1; the v2 API uses distinct paths instead.
+ */
+ public static void invokeDeleteFromV1Params(
+ CoreContainer coreContainer, SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+ final String requestId = req.getParams().get(REQUESTID);
+ final boolean flush = req.getParams().getBool(CollectionAdminParams.FLUSH, false);
+
+ if (requestId == null && !flush) {
+ throw new SolrException(
+ ErrorCode.BAD_REQUEST, "Either requestid or flush parameter must be specified.");
+ }
+ if (requestId != null && flush) {
+ throw new SolrException(
+ ErrorCode.BAD_REQUEST,
+ "Both requestid and flush parameters can not be specified together.");
+ }
+
+ final ClusterCommands api = new ClusterCommands(coreContainer, req, rsp);
+ final DeleteClusterCommandStatusResponse jerseyResponse =
+ flush ? api.deleteAllClusterCommandStatuses() : api.deleteClusterCommandStatus(requestId);
+ V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, jerseyResponse);
+ }
+
+ private boolean overseerCollectionQueueContains(String asyncId) throws Exception {
+ return coreContainer
+ .getZkController()
+ .getOverseerCollectionQueue()
+ .containsTaskWithRequestId(ASYNC, asyncId);
+ }
+
+ private static void setStatus(
+ GetClusterCommandStatusResponse response, RequestStatusState state, String requestId) {
+ final CommandStatus status = new CommandStatus();
+ status.state = CommandStatus.State.valueOf(state.name());
+ status.msg = statusMessage(state, requestId);
+ response.status = status;
+ }
+
+ private static String statusMessage(RequestStatusState state, String requestId) {
+ return switch (state) {
+ case RUNNING -> "found [" + requestId + "] in running tasks";
+ case COMPLETED -> "found [" + requestId + "] in completed tasks";
+ case FAILED -> "found [" + requestId + "] in failed tasks";
+ case SUBMITTED -> "found [" + requestId + "] in submitted tasks";
+ default -> "Did not find [" + requestId + "] in any tasks queue";
+ };
+ }
+
+ private static void copyStoredCommandResponse(
+ GetClusterCommandStatusResponse response, NamedList<Object> stored) {
+ if (stored == null) {
+ return;
+ }
+ for (Map.Entry<String, Object> entry : stored) {
+ final String key = entry.getKey();
+ if ("responseHeader".equals(key) || "error".equals(key) || "status".equals(key)) {
+ continue;
+ }
+ response.setUnknownProperty(key, entry.getValue());
+ }
+ }
+
+ /**
+ * v1 SolrJ reads {@code status} as a {@link NamedList}. Squash leaves the JAX-RS {@link
+ * CommandStatus} POJO in place; replace it so existing clients keep working.
+ */
+ private static void convertStatusToNamedList(SolrQueryResponse rsp) {
+ final NamedList<Object> values = rsp.getValues();
+ final int idx = values.indexOf("status", 0);
+ if (idx < 0) {
+ return;
+ }
+ final Object statusVal = values.getVal(idx);
+ if (statusVal instanceof CommandStatus commandStatus) {
+ final SimpleOrderedMap<String> status = new SimpleOrderedMap<>();
+ status.add("state", commandStatus.state.getKey());
+ status.add("msg", commandStatus.msg);
+ values.setVal(idx, status);
+ }
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
index b41640f1534..4fc458bc589 100644
--- a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
@@ -17,7 +17,6 @@
package org.apache.solr.handler;
-import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
import static org.apache.solr.common.params.CommonParams.ACTION;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -69,15 +68,6 @@ public class V2ClusterAPIMappingTest extends SolrTestCaseJ4 {
apiBag.registerObject(clusterAPI.commands);
}
- @Test
- public void testAsyncCommandStatusAllParams() throws Exception {
- final SolrParams v1Params =
- captureConvertedV1Params("/cluster/command-status/someId", "GET", null);
-
- assertEquals(CollectionParams.CollectionAction.REQUESTSTATUS.lowerName, v1Params.get(ACTION));
- assertEquals("someId", v1Params.get(REQUESTID));
- }
-
@Test
public void testClusterOverseerAllParams() throws Exception {
final SolrParams v1Params = captureConvertedV1Params("/cluster/overseer", "GET", null);
@@ -92,15 +82,6 @@ public class V2ClusterAPIMappingTest extends SolrTestCaseJ4 {
assertEquals(CollectionAction.CLUSTERSTATUS.lowerName, v1Params.get(ACTION));
}
- @Test
- public void testDeleteCommandStatusAllParams() throws Exception {
- final SolrParams v1Params =
- captureConvertedV1Params("/cluster/command-status/someId", "DELETE", null);
-
- assertEquals(CollectionParams.CollectionAction.DELETESTATUS.lowerName, v1Params.get(ACTION));
- assertEquals("someId", v1Params.get(REQUESTID));
- }
-
@Test
public void testAddRoleAllParams() throws Exception {
final SolrParams v1Params =
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/ClusterCommandsTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/ClusterCommandsTest.java
new file mode 100644
index 00000000000..dc6857cfa28
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/ClusterCommandsTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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 java.util.concurrent.TimeUnit;
+import org.apache.solr.client.api.model.DeleteClusterCommandStatusResponse;
+import org.apache.solr.client.api.model.GetClusterCommandStatusResponse;
+import org.apache.solr.client.api.model.GetClusterCommandStatusResponse.CommandStatus.State;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.request.ClusterApi;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * HTTP tests for {@code GET/DELETE /api/cluster/commands} via the generated SolrJ client classes.
+ */
+public class ClusterCommandsTest extends SolrCloudTestCase {
+
+ public static final int MAX_WAIT_TIMEOUT = 30;
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(1)
+ .addConfig(
+ "conf1", TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
+ .configure();
+ }
+
+ @Test
+ public void testGetCommandStatusNotFound() throws Exception {
+ GetClusterCommandStatusResponse rsp =
+ new ClusterApi.GetClusterCommandStatus("does-not-exist").process(cluster.getSolrClient());
+
+ assertNotNull(rsp);
+ assertNull(rsp.error);
+ assertEquals(State.NOT_FOUND, rsp.status.state);
+ assertEquals("Did not find [does-not-exist] in any tasks queue", rsp.status.msg);
+ }
+
+ @Test
+ public void testGetAndDeleteSingleCommandStatus() throws Exception {
+ final SolrClient client = cluster.getSolrClient();
+ final String collection = "cluster-commands-single";
+ final String asyncId =
+ CollectionAdminRequest.createCollection(collection, "conf1", 1, 1).processAsync(client);
+
+ GetClusterCommandStatusResponse getRsp = waitForCompleted(asyncId, client);
+ assertEquals(State.COMPLETED, getRsp.status.state);
+ assertEquals("found [" + asyncId + "] in completed tasks", getRsp.status.msg);
+ assertTrue(
+ "completed create should include sub-responses from the original command",
+ getRsp.unknownProperties().containsKey("success")
+ || getRsp.unknownProperties().containsKey("failure"));
+
+ DeleteClusterCommandStatusResponse deleteRsp =
+ new ClusterApi.DeleteClusterCommandStatus(asyncId).process(client);
+ assertEquals("successfully removed stored response for [" + asyncId + "]", deleteRsp.status);
+
+ GetClusterCommandStatusResponse afterDelete =
+ new ClusterApi.GetClusterCommandStatus(asyncId).process(client);
+ assertEquals(State.NOT_FOUND, afterDelete.status.state);
+ }
+
+ @Test
+ public void testDeleteUnknownCommandStatus() throws Exception {
+ DeleteClusterCommandStatusResponse rsp =
+ new ClusterApi.DeleteClusterCommandStatus("foo").process(cluster.getSolrClient());
+ assertEquals("[foo] not found in stored responses", rsp.status);
+ }
+
+ @Test
+ public void testDeleteAllCommandStatuses() throws Exception {
+ final SolrClient client = cluster.getSolrClient();
+ final String id1 =
+ CollectionAdminRequest.createCollection("cluster-commands-flush-1", "conf1", 1, 1)
+ .processAsync(client);
+ final String id2 =
+ CollectionAdminRequest.createCollection("cluster-commands-flush-2", "conf1", 1, 1)
+ .processAsync(client);
+
+ waitForCompleted(id1, client);
+ waitForCompleted(id2, client);
+
+ DeleteClusterCommandStatusResponse flushRsp =
+ new ClusterApi.DeleteAllClusterCommandStatuses().process(client);
+ assertEquals("successfully cleared stored collection api responses", flushRsp.status);
+
+ assertEquals(
+ State.NOT_FOUND, new ClusterApi.GetClusterCommandStatus(id1).process(client).status.state);
+ assertEquals(
+ State.NOT_FOUND, new ClusterApi.GetClusterCommandStatus(id2).process(client).status.state);
+ }
+
+ private static GetClusterCommandStatusResponse waitForCompleted(String id, SolrClient client)
+ throws Exception {
+ GetClusterCommandStatusResponse rsp = null;
+ long endTime = System.nanoTime() + TimeUnit.SECONDS.toNanos(MAX_WAIT_TIMEOUT);
+ while (System.nanoTime() < endTime) {
+ rsp = new ClusterApi.GetClusterCommandStatus(id).process(client);
+ State state = rsp.status.state;
+ assumeTrue("Error creating collection - skipping test", state != State.FAILED);
+ if (state == State.COMPLETED) {
+ return rsp;
+ }
+ TimeUnit.SECONDS.sleep(1);
+ }
+ assumeTrue(
+ "Timed out waiting for async request " + id,
+ rsp != null && State.COMPLETED.equals(rsp.status.state));
+ return rsp;
+ }
+}
diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc
index 0ed88262b49..71a20e63acf 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc
@@ -117,7 +117,7 @@ V2 API::
====
[source,bash]
----
-curl -X GET http://localhost:8983/api/cluster/command-status/1000
+curl -X GET http://localhost:8983/api/cluster/commands/1000
----
====
======
@@ -205,13 +205,13 @@ V2 API::
Delete a single request response:
[source,bash]
----
-curl -X DELETE http://localhost:8983/api/cluster/command-status/1000
+curl -X DELETE http://localhost:8983/api/cluster/commands/1000
----
Flush out all stored completed and failed async request responses:
[source,bash]
----
-curl -X DELETE http://localhost:8983/api/cluster/command-status?flush=true
+curl -X DELETE http://localhost:8983/api/cluster/commands
----
====
======
@@ -234,7 +234,8 @@ The request ID of the asynchronous call whose stored response should be cleared.
|Optional |Default: none
|===
+
-Set to `true` to clear all stored completed and failed async request responses.
+v1 only. Set to `true` to clear all stored completed and failed async request responses.
+The v2 API uses `DELETE /api/cluster/commands` for the same operation and does not take this parameter.
=== Examples using DELETESTATUS
(solr) 02/09: SOLR-18152: improve body format OpenAPI annotations to fully communicate SchemaDesigner API (#4819)
Posted by ds...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
commit ae0c9c40a1338f7b8f87f1d6c83f4098a4ef25e5
Author: Eric Pugh <ep...@opensourceconnections.com>
AuthorDate: Fri Sep 4 12:46:43 2026 -0400
SOLR-18152: improve body format OpenAPI annotations to fully communicate SchemaDesigner API (#4819)
(cherry picked from commit 0b1d3f75d5c1bdacd966584299b47366537395f8)
---
.../client/api/endpoint/SchemaDesignerApi.java | 8 +-
.../api/model/SchemaDesignerAddRequestBody.java | 5 -
.../api/model/SchemaDesignerUpdateRequestBody.java | 3 +
.../api/model/UpsertDynamicFieldOperation.java | 3 +
.../client/api/model/UpsertFieldOperation.java | 3 +
.../client/api/model/UpsertFieldTypeOperation.java | 10 +-
.../SchemaChangeOperationSerializationTest.java | 4 +-
.../solr/handler/admin/api/UpdateSchema.java | 2 +-
.../java/org/apache/solr/schema/SchemaManager.java | 4 +-
.../admin/api/V2UpdateSchemaErrorCaseTests.java | 2 +-
.../handler/designer/TestSchemaDesignerSolrJ.java | 4 +-
.../solrj/src/resources/java-template/api.mustache | 11 +-
.../solr/webapp/AdminUiSchemaDesignerTest.java | 46 +--
.../web/js/angular/controllers/schema-designer.js | 460 ++++++++++++---------
solr/webapp/web/js/angular/services.js | 17 +-
solr/webapp/web/partials/schema-designer.html | 4 +-
16 files changed, 341 insertions(+), 245 deletions(-)
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/SchemaDesignerApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/SchemaDesignerApi.java
index 572891d6ec5..d319471857d 100644
--- a/solr/api/src/java/org/apache/solr/client/api/endpoint/SchemaDesignerApi.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/SchemaDesignerApi.java
@@ -16,6 +16,7 @@
*/
package org.apache.solr.client.api.endpoint;
+import static org.apache.solr.client.api.util.Constants.ADDTL_FIELDS_PROPERTY;
import static org.apache.solr.client.api.util.Constants.GENERIC_ENTITY_PROPERTY;
import io.swagger.v3.oas.annotations.Operation;
@@ -147,7 +148,12 @@ public interface SchemaDesignerApi {
SchemaDesignerUpdateResponse updateSchemaObject(
@PathParam("configSet") String configSet,
@QueryParam("schemaVersion") Integer schemaVersion,
- SchemaDesignerUpdateRequestBody requestBody)
+ @RequestBody(
+ extensions = {
+ @Extension(
+ properties = {@ExtensionProperty(name = ADDTL_FIELDS_PROPERTY, value = "true")})
+ })
+ SchemaDesignerUpdateRequestBody requestBody)
throws Exception;
@PUT
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerAddRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerAddRequestBody.java
index 821cabc4246..25e0da72a79 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerAddRequestBody.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerAddRequestBody.java
@@ -17,7 +17,6 @@
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;
/**
@@ -27,19 +26,15 @@ import java.util.Map;
*/
public class SchemaDesignerAddRequestBody {
- @Schema(name = "addField")
@JsonProperty("add-field")
public Map<String, Object> addField;
- @Schema(name = "addDynamicField")
@JsonProperty("add-dynamic-field")
public Map<String, Object> addDynamicField;
- @Schema(name = "addCopyField")
@JsonProperty("add-copy-field")
public Map<String, Object> addCopyField;
- @Schema(name = "addFieldType")
@JsonProperty("add-field-type")
public Map<String, Object> addFieldType;
}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerUpdateRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerUpdateRequestBody.java
index 54b9bb9e56c..e88114462e5 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerUpdateRequestBody.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/SchemaDesignerUpdateRequestBody.java
@@ -19,6 +19,7 @@ package org.apache.solr.client.api.model;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
import java.util.HashMap;
import java.util.Map;
@@ -28,6 +29,7 @@ import java.util.Map;
* indexed}, {@code stored}, {@code analyzer}, {@code copyDest}) are captured via the dynamic {@code
* additionalProperties} map and forwarded to the Schema API.
*/
+@Schema(additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
public class SchemaDesignerUpdateRequestBody {
@JsonProperty public String name;
@@ -36,6 +38,7 @@ public class SchemaDesignerUpdateRequestBody {
// Accessed via @JsonAnyGetter / @JsonAnySetter for JSON (de)serialization.
public Map<String, Object> additionalProperties = new HashMap<>();
+ @Schema(hidden = true)
@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return additionalProperties;
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/UpsertDynamicFieldOperation.java b/solr/api/src/java/org/apache/solr/client/api/model/UpsertDynamicFieldOperation.java
index db010fbacca..2a0376fafc3 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/UpsertDynamicFieldOperation.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/UpsertDynamicFieldOperation.java
@@ -19,9 +19,11 @@ package org.apache.solr.client.api.model;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
import java.util.HashMap;
import java.util.Map;
+@Schema(additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
public class UpsertDynamicFieldOperation extends SchemaChange {
@JsonProperty public String name;
@JsonProperty public String type;
@@ -29,6 +31,7 @@ public class UpsertDynamicFieldOperation extends SchemaChange {
// Used for setting index and stored settings, etc.
private Map<String, Object> additionalProperties = new HashMap<>();
+ @Schema(hidden = true)
@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return additionalProperties;
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldOperation.java b/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldOperation.java
index c6a7d1c3c32..6946f3536c8 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldOperation.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldOperation.java
@@ -19,9 +19,11 @@ package org.apache.solr.client.api.model;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
import java.util.HashMap;
import java.util.Map;
+@Schema(additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
public class UpsertFieldOperation extends SchemaChange {
@JsonProperty public String name;
@@ -30,6 +32,7 @@ public class UpsertFieldOperation extends SchemaChange {
// Used for setting index and stored settings, etc.
private Map<String, Object> additionalProperties = new HashMap<>();
+ @Schema(hidden = true)
@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return additionalProperties;
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldTypeOperation.java b/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldTypeOperation.java
index 6a517830b2e..60ee25122c8 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldTypeOperation.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/UpsertFieldTypeOperation.java
@@ -19,18 +19,26 @@ package org.apache.solr.client.api.model;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
import java.util.HashMap;
import java.util.Map;
+@Schema(additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
public class UpsertFieldTypeOperation extends SchemaChange {
@JsonProperty public String name;
+ // Field named to match what the OpenAPI-generated SolrJ client derives as a Java-safe
+ // identifier for the reserved word "class" (see api.mustache's {{name}} usage) -- the
+ // SolrJ codegen assigns into this field by that exact name, so a hand-picked name here
+ // (e.g. "className") would compile-fail the generated client the moment this class stops
+ // being shielded from per-field setter generation (see SchemaChange's oneOf discriminator).
@JsonProperty("class")
- public String className;
+ public String propertyClass;
// Used for setting analyzers, index and stored settings, etc.
private Map<String, Object> additionalProperties = new HashMap<>();
+ @Schema(hidden = true)
@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return additionalProperties;
diff --git a/solr/api/src/test/org/apache/solr/client/api/model/SchemaChangeOperationSerializationTest.java b/solr/api/src/test/org/apache/solr/client/api/model/SchemaChangeOperationSerializationTest.java
index 48afd83e694..a0606bd6d3d 100644
--- a/solr/api/src/test/org/apache/solr/client/api/model/SchemaChangeOperationSerializationTest.java
+++ b/solr/api/src/test/org/apache/solr/client/api/model/SchemaChangeOperationSerializationTest.java
@@ -58,7 +58,7 @@ public class SchemaChangeOperationSerializationTest extends SolrTestCase {
assertThat(parsedGeneric, instanceOf(UpsertFieldTypeOperation.class));
final var parsedSpecific = (UpsertFieldTypeOperation) parsedGeneric;
assertEquals("my-new-field-type", parsedSpecific.name);
- assertEquals("org.apache.my.ClassName", parsedSpecific.className);
+ assertEquals("org.apache.my.ClassName", parsedSpecific.propertyClass);
// Arbitrary properties are put in a map, and can contain nesting
assertEquals(100, parsedSpecific.getAdditionalProperties().get("positionIncrementGap"));
assertThat(parsedSpecific.getAdditionalProperties().get("analyzer"), instanceOf(Map.class));
@@ -272,7 +272,7 @@ public class SchemaChangeOperationSerializationTest extends SolrTestCase {
assertThat(parsedGeneric, instanceOf(UpsertFieldTypeOperation.class));
final var parsedSpecific = (UpsertFieldTypeOperation) parsedGeneric;
assertEquals("my-new-field-type", parsedSpecific.name);
- assertEquals("org.apache.my.ClassName", parsedSpecific.className);
+ assertEquals("org.apache.my.ClassName", parsedSpecific.propertyClass);
// Arbitrary properties are put in a map, and can contain nesting
assertEquals(100, parsedSpecific.getAdditionalProperties().get("positionIncrementGap"));
assertThat(parsedSpecific.getAdditionalProperties().get("analyzer"), instanceOf(Map.class));
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateSchema.java b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateSchema.java
index 1bb3f51c4c3..6bb00ea00f6 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateSchema.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/UpdateSchema.java
@@ -122,7 +122,7 @@ public class UpdateSchema extends JerseyResource implements UpdateSchemaApi {
ensureSchemaMutable();
ensureRequiredRequestBodyProvided(requestBody);
ensureRequiredParameterProvided("fieldTypeName", fieldTypeName);
- ensureRequiredParameterProvided("class", requestBody.className);
+ ensureRequiredParameterProvided("class", requestBody.propertyClass);
requestBody.operationType = "add-field-type";
runWithSchemaManager(List.of(requestBody), response);
diff --git a/solr/core/src/java/org/apache/solr/schema/SchemaManager.java b/solr/core/src/java/org/apache/solr/schema/SchemaManager.java
index a65eb18df6a..05efe512c21 100644
--- a/solr/core/src/java/org/apache/solr/schema/SchemaManager.java
+++ b/solr/core/src/java/org/apache/solr/schema/SchemaManager.java
@@ -240,7 +240,7 @@ public class SchemaManager {
public boolean perform(SchemaChange op, SchemaManager mgr) throws SchemaOperationException {
final var addFieldTypeOp = (UpsertFieldTypeOperation) op;
String name = ensureNotNull("name", addFieldTypeOp.name);
- String className = ensureNotNull("class", addFieldTypeOp.className);
+ String className = ensureNotNull("class", addFieldTypeOp.propertyClass);
try {
FieldType fieldType =
mgr.managedIndexSchema.newFieldType(name, className, convertToMap(addFieldTypeOp));
@@ -420,7 +420,7 @@ public class SchemaManager {
public boolean perform(SchemaChange op, SchemaManager mgr) throws SchemaOperationException {
final var replaceFieldTypeOp = (UpsertFieldTypeOperation) op;
String name = ensureNotNull("name", replaceFieldTypeOp.name);
- String className = ensureNotNull("class", replaceFieldTypeOp.className);
+ String className = ensureNotNull("class", replaceFieldTypeOp.propertyClass);
try {
mgr.managedIndexSchema =
mgr.managedIndexSchema.replaceFieldType(
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/V2UpdateSchemaErrorCaseTests.java b/solr/core/src/test/org/apache/solr/handler/admin/api/V2UpdateSchemaErrorCaseTests.java
index 54d063dc31e..d981f572a0b 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/V2UpdateSchemaErrorCaseTests.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/V2UpdateSchemaErrorCaseTests.java
@@ -123,7 +123,7 @@ public class V2UpdateSchemaErrorCaseTests extends SolrTestCase {
@Test
public void testAddFieldTypeOperationRequiresTypeNameAndClass() {
final var noTypeOp = new UpsertFieldTypeOperation();
- noTypeOp.className = "solr.TextField";
+ noTypeOp.propertyClass = "solr.TextField";
var thrown =
expectThrows(
SolrException.class,
diff --git a/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerSolrJ.java b/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerSolrJ.java
index d755b6964d9..9d3a4fe1384 100644
--- a/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerSolrJ.java
+++ b/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerSolrJ.java
@@ -123,7 +123,9 @@ public class TestSchemaDesignerSolrJ extends SolrCloudTestCase {
var update = new SchemaDesignerApi.UpdateSchemaObject(configSet);
update.setSchemaVersion(schemaVersion);
update.setName("keywords");
- update.setAdditionalProperties(Map.of("type", "string", "stored", true, "multiValued", true));
+ update.setAdditionalProperty("type", "string");
+ update.setAdditionalProperty("stored", true);
+ update.setAdditionalProperty("multiValued", true);
SchemaDesignerUpdateResponse updateResp = update.process(cluster.getSolrClient());
assertNotNull(updateResp.field);
assertEquals("field", updateResp.updateType);
diff --git a/solr/solrj/src/resources/java-template/api.mustache b/solr/solrj/src/resources/java-template/api.mustache
index 81cdff1b3a8..4aae4828561 100644
--- a/solr/solrj/src/resources/java-template/api.mustache
+++ b/solr/solrj/src/resources/java-template/api.mustache
@@ -179,13 +179,18 @@ public class {{classname}} {
{{#bodyParam}}
{{#vars}}
// TODO find a way to add required parameters in the request body to the class constructor
+ // The setter parameter (and the requestBody.<field> access below) both use the
+ // "name" var: openapi-generator's Java-safe sanitization of this property's wire
+ // name (e.g. the reserved word "class" becomes "propertyClass"). The hand-written
+ // requestBody model class's field MUST be named to match exactly, or the field
+ // access below fails to compile.
{{#description}}
/**
- * @param {{baseName}} {{description}}
+ * @param {{name}} {{description}}
*/
{{/description}}
- public void {{setter}}({{{dataType}}} {{baseName}}) {
- this.requestBody.{{baseName}} = {{baseName}};
+ public void {{setter}}({{{dataType}}} {{name}}) {
+ this.requestBody.{{name}} = {{name}};
}
{{/vars}}
diff --git a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSchemaDesignerTest.java b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSchemaDesignerTest.java
index 85a76aec800..739c6312e6e 100644
--- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSchemaDesignerTest.java
+++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSchemaDesignerTest.java
@@ -16,7 +16,6 @@
*/
package org.apache.solr.webapp;
-import org.apache.lucene.tests.util.LuceneTestCase;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
@@ -25,11 +24,9 @@ import org.openqa.selenium.WebElement;
* Happy-path test of the Schema Designer screen: create a new schema, paste a sample document and
* let the designer analyze it.
*
- * <p>AwaitsFix: the designer backend transiently fails its own prep/analyze calls ("version
- * mismatch, retry", "Error loading solr config") when driven at automation speed, making this test
- * flaky even with retries.
+ * <p>The Analyze action remains disabled until creation of the mutable schema has completed, so a
+ * fast user cannot race the prep and analyze requests.
*/
-@LuceneTestCase.AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/SOLR-18347")
public class AdminUiSchemaDesignerTest extends AdminUiTestBase {
@Test
@@ -47,34 +44,17 @@ public class AdminUiSchemaDesignerTest extends AdminUiTestBase {
WebElement sampleDocs = waitFor(By.cssSelector("#sample-docs textarea#document"));
sampleDocs.clear();
sampleDocs.sendKeys("[{\"id\":\"1\",\"designer_title\":\"Hello Designer\"}]");
- click(By.id("analyze"));
+ click(By.cssSelector("#analyze:not([disabled])"));
- // the analyzed schema lists the field derived from the sample doc. The designer
- // backend transiently fails its own calls ("version mismatch, retry", "Error
- // loading solr config") and surfaces an error dialog - dismiss it and analyze
- // again, with a generous budget since each round trips several requests
- long deadlineNanos = System.nanoTime() + WAIT_TIMEOUT.multipliedBy(3).toNanos();
- boolean analyzed = false;
- while (!analyzed && System.nanoTime() < deadlineNanos) {
- analyzed = driver.getPageSource().contains("designer_title");
- if (!analyzed) {
- for (String dismissButton : new String[] {"Reload Schema", "OK"}) {
- driver.findElements(By.xpath("//button[contains(., '" + dismissButton + "')]")).stream()
- .filter(WebElement::isDisplayed)
- .findFirst()
- .ifPresent(WebElement::click);
- }
- driver.findElements(By.id("analyze")).stream()
- .filter(WebElement::isDisplayed)
- .findFirst()
- .ifPresent(WebElement::click);
- Thread.sleep(500);
- }
- }
- assertTrue("Analyzed schema should list the sample doc field", analyzed);
- // the designer's own API calls (prep/analyze/luke against its temp core) error
- // transiently while it persists and reloads the schema - it recovers via its retry
- // dialog, so only unrelated console errors fail the test
- assertNoSevereConsoleErrors("schema-designer/", "._designer_");
+ waitForPageContains("designer_title");
+ assertNoSevereConsoleErrors();
+
+ // add a field through the UI
+ click(By.cssSelector("#addField"));
+ setText(By.id("add_name"), "extra_test_field");
+ click(By.xpath("//button[@ng-click='addField()']"));
+
+ waitForPageContains("extra_test_field");
+ assertNoSevereConsoleErrors();
}
}
diff --git a/solr/webapp/web/js/angular/controllers/schema-designer.js b/solr/webapp/web/js/angular/controllers/schema-designer.js
index 7ec4282bb05..a8d4c963245 100644
--- a/solr/webapp/web/js/angular/controllers/schema-designer.js
+++ b/solr/webapp/web/js/angular/controllers/schema-designer.js
@@ -15,7 +15,7 @@
limitations under the License.
*/
-solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout, $cookies, $window, Constants, SchemaDesigner, ConfigSetFiles, Luke) {
+solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout, $cookies, $window, Constants, SchemaDesigner, SchemaDesignerV2, ConfigSetFiles, Luke) {
$scope.resetMenu("schema-designer", Constants.IS_ROOT_PAGE);
$scope.schemas = [];
@@ -24,6 +24,7 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
$scope.sortableFields = [];
$scope.hlFields = [];
$scope.types = [];
+ $scope.preparingSchema = false;
$scope.onWarning = function (warnMsg, warnDetails) {
$scope.updateWorking = false;
@@ -34,6 +35,7 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
$scope.onError = function (errorMsg, errorCode, errorDetails) {
$scope.updateWorking = false;
+ $scope.preparingSchema = false;
delete $scope.updateStatusMessage;
$scope.designerAPIError = errorMsg;
if (errorDetails) {
@@ -68,20 +70,38 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
} // else 500 errors get the top-level error message
};
- $scope.errorHandler = function (e) {
- var error = e.data && e.data.error ? e.data.error : null;
+ // Shared by $scope.errorHandler (v1 $http) and $scope.errorHandlerV2 (v2 generated-client) below:
+ // given a structured API error (or null, for a network-level failure like a timeout), drives the
+ // same local error dialog via $scope.onError.
+ function reportApiFailure(error, errorDetails, fallbackPath, fallbackCode, extraFailureHint) {
if (error) {
- $scope.onError(error.msg, error.code, e.data.errorDetails);
+ $scope.onError(error.msg, error.code, errorDetails);
} else {
- // when a timeout occurs, the error details are sparse so just give the user a hint that something was off
- var path = e.config && e.config.url ? e.config.url : "/api/schema-designer";
- var reloadMsg = "";
- if (path.includes("/analyze")) {
- reloadMsg = " Re-try analyzing your sample docs by clicking on 'Analyze Documents' again."
- }
- $scope.onError("Request to "+path+" failed!", 408,
- {"error":"Most likely the request timed out; check server log for more details."+reloadMsg});
+ $scope.onError("Request to "+fallbackPath+" failed!", fallbackCode,
+ {"error":"Most likely the request timed out; check server log for more details."+(extraFailureHint || "")});
}
+ }
+
+ $scope.errorHandler = function (e) {
+ var error = e.data && e.data.error ? e.data.error : null;
+ // when a timeout occurs, the error details are sparse so just give the user a hint that something was off
+ var path = e.config && e.config.url ? e.config.url : "/api/schema-designer";
+ var reloadMsg = path.includes("/analyze")
+ ? " Re-try analyzing your sample docs by clicking on 'Analyze Documents' again."
+ : "";
+ reportApiFailure(error, e.data && e.data.errorDetails, path, 408, reloadMsg);
+ };
+
+ // Error handler for SchemaDesignerV2 (generated OpenAPI client) callbacks: response is the raw
+ // superagent response (may be undefined for a network-level failure like a timeout). This deliberately
+ // does NOT go through the shared ApiErrorHandler service -- app.js's httpInterceptor already carves
+ // out /api/schema-designer/ from the global 401/403 handling so a failure here degrades this one
+ // screen instead of forcing a full login redirect mid-design-session; this mirrors that same intent
+ // for v2 calls by driving the same local error dialog as $scope.errorHandler.
+ $scope.errorHandlerV2 = function (response) {
+ var data = (response && response.body) || {};
+ var path = (response && response.req && response.req.url) || "/api/schema-designer";
+ reportApiFailure(data.error, data.errorDetails, path, (response && response.status) || 408);
};
$scope.closeWarnDialog = function () {
@@ -149,36 +169,40 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
// query form
$scope.query = {q: '*:*', sortBy: 'score', sortDir: 'desc'};
- SchemaDesigner.get({path: "configs"}, function (data) {
+ SchemaDesignerV2.listDesignerConfigs(function (error, data, response) {
+ $timeout(function () {
+ if (error) {
+ if (response && (response.status === 401 || response.status === 403)) {
+ $scope.isSchemaDesignerEnabled = false;
+ $scope.hideAll();
+ }
+ return;
+ }
- $scope.schemas = [];
- $scope.publishedSchemas = ["_default"];
+ $scope.schemas = [];
+ $scope.publishedSchemas = ["_default"];
- for (var s in data.configSets) {
- // 1 means published but not editable
- if (data.configSets[s] !== 1) {
- $scope.schemas.push(s);
- }
+ for (var s in data.configSets) {
+ // 1 means published but not editable
+ if (data.configSets[s] !== 1) {
+ $scope.schemas.push(s);
+ }
- // 0 means not published yet (so can't copy from it yet)
- if (data.configSets[s] > 0) {
- $scope.publishedSchemas.push(s);
+ // 0 means not published yet (so can't copy from it yet)
+ if (data.configSets[s] > 0) {
+ $scope.publishedSchemas.push(s);
+ }
}
- }
- $scope.schemas.sort();
- $scope.publishedSchemas.sort();
+ $scope.schemas.sort();
+ $scope.publishedSchemas.sort();
- // if no schemas available to select, open the pop-up immediately
- if ($scope.schemas.length === 0) {
- $scope.firstSchemaMessage = true;
- $scope.showNewSchemaDialog();
- }
- }, function(e) {
- if (e.status === 401 || e.status === 403) {
- $scope.isSchemaDesignerEnabled = false;
- $scope.hideAll();
- }
+ // if no schemas available to select, open the pop-up immediately
+ if ($scope.schemas.length === 0) {
+ $scope.firstSchemaMessage = true;
+ $scope.showNewSchemaDialog();
+ }
+ });
});
};
@@ -239,26 +263,31 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
}
$scope.resetSchema();
- var params = {configSet: $scope.currentSchema};
- SchemaDesigner.get(params, function (data) {
- $scope.currentSchema = data.configSet;
- $("#select-schema").trigger("chosen:updated");
-
- $scope.confirmSchema = data.configSet;
- $scope.collectionsForConfig = data.collections;
- $scope.hasDocsOnServer = data.numDocs > 0;
- $scope.published = data.published;
- $scope.initDesignerSettingsFromResponse(data);
- if ($scope.collectionsForConfig && $scope.collectionsForConfig.length > 0) {
- $scope.showConfirmEditSchema = true;
- } else {
- if ($scope.hasDocsOnServer || $scope.published) {
- $scope.doAnalyze();
+ SchemaDesignerV2.getInfo($scope.currentSchema, function (error, data, response) {
+ $timeout(function () {
+ if (error) {
+ $scope.errorHandlerV2(response);
+ return;
+ }
+ $scope.currentSchema = data.configSet;
+ $("#select-schema").trigger("chosen:updated");
+
+ $scope.confirmSchema = data.configSet;
+ $scope.collectionsForConfig = data.collections;
+ $scope.hasDocsOnServer = data.numDocs > 0;
+ $scope.published = data.published;
+ $scope.initDesignerSettingsFromResponse(data);
+ if ($scope.collectionsForConfig && $scope.collectionsForConfig.length > 0) {
+ $scope.showConfirmEditSchema = true;
} else {
- $scope.sampleMessage = "Please upload or paste some sample documents to build the '" + $scope.currentSchema + "' schema.";
+ if ($scope.hasDocsOnServer || $scope.published) {
+ $scope.doAnalyze();
+ } else {
+ $scope.sampleMessage = "Please upload or paste some sample documents to build the '" + $scope.currentSchema + "' schema.";
+ }
}
- }
- }, $scope.errorHandler);
+ });
+ });
};
$scope.showNewSchemaDialog = function () {
@@ -303,9 +332,17 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
$scope.currentSchema = $scope.newSchema;
$scope.sampleMessage = "Please upload or paste some sample documents to analyze for building the '" + $scope.currentSchema + "' schema.";
- SchemaDesigner.post({path: "prep", configSet: $scope.newSchema, copyFrom: $scope.copyFrom}, null, function (data) {
- $scope.initDesignerSettingsFromResponse(data);
- }, $scope.errorHandler);
+ $scope.preparingSchema = true;
+ SchemaDesignerV2.prepNewSchema($scope.newSchema, {copyFrom: $scope.copyFrom}, function (error, data, response) {
+ $timeout(function () {
+ if (error) {
+ $scope.errorHandlerV2(response);
+ return;
+ }
+ $scope.preparingSchema = false;
+ $scope.initDesignerSettingsFromResponse(data);
+ });
+ });
};
$scope.cancelAddSchema = function () {
@@ -451,9 +488,11 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
// re-apply the filters on the updated schema
$scope.onTreeFilterOptionChanged();
- // Load the Luke schema
- Luke.schema({core: data.core}, function (schema) {
- Luke.raw({core: data.core}, function (index) {
+ // Load the Luke schema. Route through the temporary collection so the request reaches its
+ // active replica even when the Admin UI is connected to a different node.
+ var lukeTarget = data.tempCollection || data.core;
+ Luke.schema({core: lukeTarget}, function (schema) {
+ Luke.raw({core: lukeTarget}, function (index) {
$scope.luke = mergeIndexAndSchemaData(index, schema.schema);
$scope.types = Object.keys(schema.schema.types);
$scope.showSchemaActions = true;
@@ -656,31 +695,37 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
}
delete $scope.addErrors; // no errors!
- SchemaDesigner.post({
- configSet: $scope.currentSchema,
- schemaVersion: $scope.schemaVersion
- }, addData, function (data) {
- if (data.errors) {
- $scope.addErrors = data.errors[0].errorMessages;
- if (typeof $scope.addErrors === "string") {
- $scope.addErrors = [$scope.addErrors];
+ SchemaDesignerV2.addSchemaObject($scope.currentSchema, {
+ schemaVersion: $scope.schemaVersion,
+ schemaDesignerAddRequestBody: addData
+ }, function (error, data, response) {
+ $timeout(function () {
+ if (error) {
+ $scope.errorHandlerV2(response);
+ return;
}
- } else {
- delete $scope.textAnalysisJson;
- $scope.added = true;
- $timeout(function () {
- $scope.showAddField = false;
- $scope.added = false;
- var nodeId = "/";
- if ("field" === $scope.adding) {
- nodeId = "field/" + ("add-dynamic-field" === command ? data.dynamicField : data.field);
- } else if ("type" === $scope.adding) {
- nodeId = "type/" + data.fieldType;
+ if (data.errors) {
+ $scope.addErrors = data.errors[0].errorMessages;
+ if (typeof $scope.addErrors === "string") {
+ $scope.addErrors = [$scope.addErrors];
}
- $scope.onSchemaUpdated(data.configSet, data, nodeId);
- }, 500);
- }
- }, $scope.errorHandler);
+ } else {
+ delete $scope.textAnalysisJson;
+ $scope.added = true;
+ $timeout(function () {
+ $scope.showAddField = false;
+ $scope.added = false;
+ var nodeId = "/";
+ if ("field" === $scope.adding) {
+ nodeId = "field/" + ("add-dynamic-field" === command ? data.dynamicField : data.field);
+ } else if ("type" === $scope.adding) {
+ nodeId = "type/" + data.fieldType;
+ }
+ $scope.onSchemaUpdated(data.configSet, data, nodeId);
+ }, 500);
+ }
+ });
+ });
}
function toSortedNameAndTypeList(fields, typeAttr) {
@@ -721,38 +766,44 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
$('#show-diff-dialog').css({left: leftPos});
}
- SchemaDesigner.get({ path: "diff", configSet: $scope.currentSchema }, function (data) {
- var diff = data.diff;
+ SchemaDesignerV2.getSchemaDiff($scope.currentSchema, function (error, data, response) {
+ $timeout(function () {
+ if (error) {
+ $scope.errorHandlerV2(response);
+ return;
+ }
+ var diff = data.diff;
- var dynamicFields = diff.dynamicFields;
- var enableDynamicFields = data.enableDynamicFields !== null ? data.enableDynamicFields : true;
- if (!enableDynamicFields) {
- dynamicFields = null;
- }
+ var dynamicFields = diff.dynamicFields;
+ var enableDynamicFields = data.enableDynamicFields !== null ? data.enableDynamicFields : true;
+ if (!enableDynamicFields) {
+ dynamicFields = null;
+ }
- $scope.diffSource = data["diff-source"];
- $scope.schemaDiff = {
- "fieldsDiff": diff.fields,
- "addedFields": [],
- "removedFields": [],
- "fieldTypesDiff": diff.fieldTypes,
- "removedTypes": [],
- "dynamicFieldsDiff": dynamicFields,
- "copyFieldsDiff": diff.copyFields
- }
- if (diff.fields && diff.fields.added) {
- $scope.schemaDiff.addedFields = toSortedFieldList(diff.fields.added);
- }
- if (diff.fields && diff.fields.removed) {
- $scope.schemaDiff.removedFields = toSortedNameAndTypeList(diff.fields.removed, "type");
- }
- if (diff.fieldTypes && diff.fieldTypes.removed) {
- $scope.schemaDiff.removedTypes = toSortedNameAndTypeList(diff.fieldTypes.removed, "class");
- }
+ $scope.diffSource = data["diff-source"];
+ $scope.schemaDiff = {
+ "fieldsDiff": diff.fields,
+ "addedFields": [],
+ "removedFields": [],
+ "fieldTypesDiff": diff.fieldTypes,
+ "removedTypes": [],
+ "dynamicFieldsDiff": dynamicFields,
+ "copyFieldsDiff": diff.copyFields
+ }
+ if (diff.fields && diff.fields.added) {
+ $scope.schemaDiff.addedFields = toSortedFieldList(diff.fields.added);
+ }
+ if (diff.fields && diff.fields.removed) {
+ $scope.schemaDiff.removedFields = toSortedNameAndTypeList(diff.fields.removed, "type");
+ }
+ if (diff.fieldTypes && diff.fieldTypes.removed) {
+ $scope.schemaDiff.removedTypes = toSortedNameAndTypeList(diff.fieldTypes.removed, "class");
+ }
- $scope.schemaDiffExists = !(diff.fields == null && diff.fieldTypes == null && dynamicFields == null && diff.copyFields == null);
- $scope.showDiff = true;
- }, $scope.errorHandler);
+ $scope.schemaDiffExists = !(diff.fields == null && diff.fieldTypes == null && dynamicFields == null && diff.copyFields == null);
+ $scope.showDiff = true;
+ });
+ });
}
$scope.togglePublish = function (event) {
@@ -789,22 +840,28 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
}
$scope.addCopyField = function () {
delete $scope.addCopyFieldErrors;
- var data = {"add-copy-field": $scope.copyField};
- SchemaDesigner.post({
- configSet: $scope.currentSchema,
- schemaVersion: $scope.schemaVersion
- }, data, function (data) {
- if (data.errors) {
- $scope.addCopyFieldErrors = data.errors[0].errorMessages;
- if (typeof $scope.addCopyFieldErrors === "string") {
- $scope.addCopyFieldErrors = [$scope.addCopyFieldErrors];
+ var copyFieldData = {"add-copy-field": $scope.copyField};
+ SchemaDesignerV2.addSchemaObject($scope.currentSchema, {
+ schemaVersion: $scope.schemaVersion,
+ schemaDesignerAddRequestBody: copyFieldData
+ }, function (error, data, response) {
+ $timeout(function () {
+ if (error) {
+ $scope.errorHandlerV2(response);
+ return;
}
- } else {
- $scope.showAddCopyField = false;
- // TODO:
- //$timeout($scope.refresh, 1500);
- }
- }, $scope.errorHandler);
+ if (data.errors) {
+ $scope.addCopyFieldErrors = data.errors[0].errorMessages;
+ if (typeof $scope.addCopyFieldErrors === "string") {
+ $scope.addCopyFieldErrors = [$scope.addCopyFieldErrors];
+ }
+ } else {
+ $scope.showAddCopyField = false;
+ // TODO:
+ //$timeout($scope.refresh, 1500);
+ }
+ });
+ });
}
$scope.toggleAnalyzer = function (analyzer) {
@@ -834,22 +891,28 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
}
var field = $scope.selectedNode.name;
- var params = {path: "sample"};
- params.configSet = $scope.currentSchema;
- params.uniqueKeyField = $scope.uniqueKeyField;
- params.field = field;
+ var opts = {uniqueKeyField: $scope.uniqueKeyField, field: field};
if ($scope.sampleDocId) {
- params.docId = $scope.sampleDocId;
+ opts.docId = $scope.sampleDocId;
} // else the server will pick the first doc with a non-empty text value for the desired field
- SchemaDesigner.get(params, function (data) {
- $scope.sampleDocId = data[$scope.uniqueKeyField];
- $scope.indexText = data[field];
- if (data.analysis && data.analysis["field_names"]) {
- $scope.result = processFieldAnalysisData(data.analysis["field_names"][field]);
- }
- }, $scope.errorHandler);
+ SchemaDesignerV2.getSampleValue($scope.currentSchema, opts, function (error, data, response) {
+ $timeout(function () {
+ if (error) {
+ $scope.errorHandlerV2(response);
+ return;
+ }
+ // FlexibleSolrJerseyResponse only declares responseHeader/error since the sample value and
+ // analysis are dynamic per-field data; read the raw parsed body instead of the typed `data`.
+ var raw = (response && response.body) || {};
+ $scope.sampleDocId = raw[$scope.uniqueKeyField];
+ $scope.indexText = raw[field];
+ if (raw.analysis && raw.analysis["field_names"]) {
+ $scope.result = processFieldAnalysisData(raw.analysis["field_names"][field]);
+ }
+ });
+ });
};
$scope.changeLanguages = function () {
@@ -881,23 +944,28 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
$scope.updateFile = function () {
var nodeId = "files/" + $scope.selectedFile;
- var params = {path: "file", file: $scope.selectedFile, configSet: $scope.currentSchema};
$scope.updateWorking = true;
$scope.updateStatusMessage = "Updating file ...";
- SchemaDesigner.put(params, $scope.fileNodeText, function (data) {
- if (data.updateFileError) {
- if (data.fileContent) {
- $scope.fileNodeText = data.fileContent;
+ SchemaDesignerV2.updateFileContents($scope.currentSchema, $scope.fileNodeText, {file: $scope.selectedFile}, function (error, data, response) {
+ $timeout(function () {
+ if (error) {
+ $scope.errorHandlerV2(response);
+ return;
}
- $scope.updateFileError = data.updateFileError;
- } else {
- delete $scope.updateFileError;
- $scope.updateStatusMessage = "File '"+$scope.selectedFile+"' updated.";
- $scope.onSchemaUpdated(data.configSet, data, nodeId);
- }
- }, $scope.errorHandler);
+ if (data.updateFileError) {
+ if (data.fileContent) {
+ $scope.fileNodeText = data.fileContent;
+ }
+ $scope.updateFileError = data.updateFileError;
+ } else {
+ delete $scope.updateFileError;
+ $scope.updateStatusMessage = "File '"+$scope.selectedFile+"' updated.";
+ $scope.onSchemaUpdated(data.configSet, data, nodeId);
+ }
+ });
+ });
};
$scope.onSelectFileNode = function (id, doSelectOnTree) {
@@ -1387,31 +1455,37 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
$scope.updateWorking = true;
$scope.updateStatusMessage = "Updating " + $scope.selectedType + " ...";
- SchemaDesigner.put({
- configSet: $scope.currentSchema,
- schemaVersion: $scope.schemaVersion
- }, putData, function (data) {
+ SchemaDesignerV2.updateSchemaObject($scope.currentSchema, {
+ schemaVersion: $scope.schemaVersion,
+ schemaDesignerUpdateRequestBody: putData
+ }, function (error, data, response) {
+ $timeout(function () {
+ if (error) {
+ $scope.errorHandlerV2(response);
+ return;
+ }
- var nodeType = data.updateType;
- $scope.schemaVersion = data.schemaVersion;
- $scope.currentSchema = data.configSet;
- $scope.core = data.core;
+ var nodeType = data.updateType;
+ $scope.schemaVersion = data.schemaVersion;
+ $scope.currentSchema = data.configSet;
+ $scope.core = data.core;
- $scope.selectedNode = data[nodeType];
- $scope.selectedNode.href = href;
- $scope.selectedNode.id = id;
+ $scope.selectedNode = data[nodeType];
+ $scope.selectedNode.href = href;
+ $scope.selectedNode.id = id;
- var name = nodeType === "field" ? $scope.selectedNode.type : $scope.selectedNode.name;
- $scope.initTypeAnalysisInfo(name, "type");
- $scope.showFieldDetails = true;
+ var name = nodeType === "field" ? $scope.selectedNode.type : $scope.selectedNode.name;
+ $scope.initTypeAnalysisInfo(name, "type");
+ $scope.showFieldDetails = true;
- if (nodeType === "field" && $scope.selectedNode.tokenized) {
- $scope.showAnalysis = true;
- $scope.updateSampleDocId();
- }
+ if (nodeType === "field" && $scope.selectedNode.tokenized) {
+ $scope.showAnalysis = true;
+ $scope.updateSampleDocId();
+ }
- $scope.onSchemaUpdated($scope.currentSchema, data, href);
- }, $scope.errorHandler);
+ $scope.onSchemaUpdated($scope.currentSchema, data, href);
+ });
+ });
};
// TODO: These are copied from analysis.js, so move to a shared location for both vs. duplicating
@@ -1489,34 +1563,38 @@ solrAdminApp.controller('SchemaDesignerController', function ($scope, $timeout,
};
$scope.doPublish = function () {
- var params = {
- path: "publish",
- configSet: $scope.currentSchema,
+ var opts = {
schemaVersion: $scope.schemaVersion,
reloadCollections: $scope.reloadOnPublish,
cleanupTemp: true,
disableDesigner: $scope.disableDesigner
};
if ($scope.newCollection && $scope.newCollection.name) {
- params.newCollection = $scope.newCollection.name;
- params.numShards = $scope.newCollection.numShards;
- params.replicationFactor = $scope.newCollection.replicationFactor;
- params.indexToCollection = $scope.newCollection.indexToCollection;
- }
- SchemaDesigner.put(params, null, function (data) {
- $scope.schemaVersion = data.schemaVersion;
- $scope.currentSchema = data.configSet;
+ opts.newCollection = $scope.newCollection.name;
+ opts.numShards = $scope.newCollection.numShards;
+ opts.replicationFactor = $scope.newCollection.replicationFactor;
+ opts.indexToCollection = $scope.newCollection.indexToCollection;
+ }
+ SchemaDesignerV2.publish($scope.currentSchema, opts, function (error, data, response) {
+ $timeout(function () {
+ if (error) {
+ $scope.errorHandlerV2(response);
+ return;
+ }
+ $scope.schemaVersion = data.schemaVersion;
+ $scope.currentSchema = data.configSet;
- delete $scope.selectedNode;
- $scope.currentSchema = "";
- delete $scope.newSchema;
- $scope.showPublish = false;
- $scope.refresh();
+ delete $scope.selectedNode;
+ $scope.currentSchema = "";
+ delete $scope.newSchema;
+ $scope.showPublish = false;
+ $scope.refresh();
- if (data.newCollection) {
- $window.location.href = "#/" + data.newCollection + "/collection-overview";
- }
- }, $scope.errorHandler);
+ if (data.newCollection) {
+ $window.location.href = "#/" + data.newCollection + "/collection-overview";
+ }
+ });
+ });
};
$scope.downloadConfig = function () {
diff --git a/solr/webapp/web/js/angular/services.js b/solr/webapp/web/js/angular/services.js
index f75a2f9d3c6..1272fceff94 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -148,6 +148,12 @@ solrAdminServices.factory('Metrics',
delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
return new solrApi.SegmentsApi();
})
+.factory('SchemaDesignerV2',
+ function() {
+ solrApi.ApiClient.instance.basePath = '/api';
+ delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+ return new solrApi.SchemaDesignerApi();
+ })
.factory('Collections',
['$resource', function ($resource) {
// v2 ClusterAPI (/api/cluster) delegates straight through to the same v1 CollectionsHandler
@@ -208,7 +214,7 @@ solrAdminServices.factory('Metrics',
// v2 NodeThreadsAPI (/api/node/threads) still just delegates straight through to the same v1
// ThreadDumpHandler, so the response shape is byte-identical -- no generated solrApi client
// class exists for it (it predates the OpenAPI-based v2 API framework), so this stays a plain
- // $resource, like SchemaDesigner/Security.
+ // $resource, like Security and (partially) SchemaDesigner.
return $resource('/api/node/threads', {'wt':'json', '_':Date.now()});
}])
.factory('Replication',
@@ -364,11 +370,16 @@ solrAdminServices.factory('Metrics',
}])
.factory('SchemaDesigner',
['$resource', function($resource) {
+ // Schema Designer's analyze (sample-doc upload/paste, dynamic content-type) and query
+ // (arbitrary forwarded Solr query params) endpoints read their request bodies/params in ways
+ // the OpenAPI-generated SchemaDesignerApi client can't express: analyze() always sends a null
+ // body (the server deliberately reads the raw content stream, dispatched by Content-Type,
+ // rather than a formal parameter) and query() takes no query params at all (the server
+ // forwards arbitrary SolrParams straight through). Both stay on this plain $resource, like
+ // Threads/Collections/ParamSet. Every other Schema Designer endpoint uses SchemaDesignerV2.
return $resource('/api/schema-designer/:configSet/:path', {wt: 'json', path: '@path', configSet: '@configSet', filePath: '@filePath', _:Date.now()}, {
get: {method: "GET"},
post: {method: "POST", timeout: 90000},
- put: {method: "PUT"},
- delete: {method: "DELETE"},
postXml: {headers: {'Content-type': 'text/xml'}, method: "POST", timeout: 90000},
postCsv: {headers: {'Content-type': 'application/csv'}, method: "POST", timeout: 90000},
upload: {method: "POST", transformRequest: angular.identity, headers: {'Content-Type': undefined}, timeout: 90000}
diff --git a/solr/webapp/web/partials/schema-designer.html b/solr/webapp/web/partials/schema-designer.html
index 4d7fbd5b4b3..c5c0e1b6e7e 100644
--- a/solr/webapp/web/partials/schema-designer.html
+++ b/solr/webapp/web/partials/schema-designer.html
@@ -487,7 +487,9 @@ limitations under the License.
</div>
<div id="analyze-buttons">
<p class="clearfix note" ng-show="sampleMessage && !fileUpload && !sampleDocuments"><span>{{sampleMessage}}</span></p>
- <button type="submit" ng-click="doAnalyze()" id="analyze" ng-show="currentSchema && (hasDocsOnServer || sampleDocuments || fileUpload)"><span>Analyze Documents</span></button>
+ <button type="submit" ng-click="doAnalyze()" id="analyze"
+ ng-show="currentSchema && (hasDocsOnServer || sampleDocuments || fileUpload)"
+ ng-disabled="preparingSchema || updateWorking"><span>Analyze Documents</span></button>
</div>
</form>
</div>
(solr) 07/09: SOLR-18403: fix snapshot-export to actually use the named snapshot (#4807)
Posted by ds...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
commit d6f4987d2a479975acf0ad9993e73a43cd9469ff
Author: Serhiy Bzhezytskyy <me...@serhiy-bzhezytskyy.com>
AuthorDate: Sat Sep 5 14:39:07 2026 +0300
SOLR-18403: fix snapshot-export to actually use the named snapshot (#4807)
(cherry picked from commit e158c4d564ae002dfc1f1718e672c8013acb7ffc)
---
.../org/apache/solr/cli/SnapshotExportTool.java | 1 +
.../apache/solr/cli/SnapshotExportToolTest.java | 82 ++++++++++++++++++++++
2 files changed, 83 insertions(+)
diff --git a/solr/core/src/java/org/apache/solr/cli/SnapshotExportTool.java b/solr/core/src/java/org/apache/solr/cli/SnapshotExportTool.java
index d71b8df8b1a..208edb56ead 100644
--- a/solr/core/src/java/org/apache/solr/cli/SnapshotExportTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/SnapshotExportTool.java
@@ -116,6 +116,7 @@ public class SnapshotExportTool extends ToolBase {
CollectionAdminRequest.Backup backup =
new CollectionAdminRequest.Backup(collectionName, snapshotName);
backup.setCommitName(snapshotName);
+ backup.setIncremental(false);
backup.setIndexBackupStrategy(CollectionAdminParams.COPY_FILES_STRATEGY);
backup.setLocation(destPath);
if (backupRepo != null) {
diff --git a/solr/core/src/test/org/apache/solr/cli/SnapshotExportToolTest.java b/solr/core/src/test/org/apache/solr/cli/SnapshotExportToolTest.java
new file mode 100644
index 00000000000..c39a38dcf5b
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cli/SnapshotExportToolTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.cli;
+
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.SolrInputDocument;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SOLR-18403: SnapshotExportTool must export the state of the named snapshot, not the live index at
+ * export time.
+ */
+public class SnapshotExportToolTest extends SolrCloudTestCase {
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ System.setProperty("solr.security.allow.paths", "*");
+ configureCluster(2).addConfig("conf", configset("cloud-minimal")).configure();
+ }
+
+ @Test
+ public void testExportUsesSnapshotStateNotLiveIndex() throws Exception {
+ CloudSolrClient client = cluster.getSolrClient();
+ String collection = "snapshotexporttest";
+ CollectionAdminRequest.createCollection(collection, "conf", 1, 1).process(client);
+ cluster.waitForActiveCollection(collection, 1, 1);
+
+ // index 5 docs, commit, snapshot at this point
+ for (int i = 0; i < 5; i++) {
+ SolrInputDocument doc = new SolrInputDocument();
+ doc.addField("id", "doc-" + i);
+ client.add(collection, doc);
+ }
+ client.commit(collection);
+
+ String snapshotName = "export-test-snap";
+ new CollectionAdminRequest.CreateSnapshot(collection, snapshotName).process(client);
+
+ // index 5 MORE docs (6-10) and commit -- live index now has 10, snapshot still reflects 5
+ for (int i = 5; i < 10; i++) {
+ SolrInputDocument doc = new SolrInputDocument();
+ doc.addField("id", "doc-" + i);
+ client.add(collection, doc);
+ }
+ client.commit(collection);
+
+ assertEquals(10, client.query(collection, params("q", "*:*")).getResults().getNumFound());
+
+ String backupLocation = createTempDir().toString();
+ SnapshotExportTool tool = new SnapshotExportTool(new DefaultToolRuntime());
+ tool.exportSnapshot(client, collection, snapshotName, backupLocation, null, null);
+
+ String restoredCollection = collection + "_restored";
+ CollectionAdminRequest.Restore restore =
+ CollectionAdminRequest.restoreCollection(restoredCollection, snapshotName)
+ .setLocation(backupLocation);
+ assertEquals(0, restore.process(client).getStatus());
+ cluster.waitForActiveCollection(restoredCollection, 1, 1);
+
+ assertEquals(
+ "export must reflect the snapshot's 5-doc state, not the live index's 10",
+ 5,
+ client.query(restoredCollection, params("q", "*:*")).getResults().getNumFound());
+ }
+}
(solr) 01/09: Pin locale to deal with flakyness in GCS testing library (#4869)
Posted by ds...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
commit 8695e6db42368fb25d910d9a43a34757f35324b9
Author: Eric Pugh <ep...@opensourceconnections.com>
AuthorDate: Fri Sep 4 07:16:13 2026 -0400
Pin locale to deal with flakyness in GCS testing library (#4869)
(cherry picked from commit 4da778a7259e103284ee02d60a9e243efe7ee3ce)
---
.../apache/solr/gcs/GCSBackupRepositoryTest.java | 26 ++++++++++++++++++++--
.../solr/gcs/LocalStorageGCSBackupRepository.java | 15 +++++++------
2 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSBackupRepositoryTest.java b/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSBackupRepositoryTest.java
index e4a2e084445..68c825777ae 100644
--- a/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSBackupRepositoryTest.java
+++ b/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSBackupRepositoryTest.java
@@ -45,6 +45,7 @@ import org.apache.solr.cloud.api.collections.AbstractBackupRepositoryTest;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.backup.repository.BackupRepository;
import org.junit.AfterClass;
+import org.junit.Assume;
import org.junit.Test;
/** Unit tests for {@link GCSBackupRepository} that use an in-memory Storage object */
@@ -123,7 +124,7 @@ public class GCSBackupRepositoryTest extends AbstractBackupRepositoryTest {
GCSBackupRepository repo = createRepositoryWithStorage(realStorage);
URI sourceDir = repo.resolve(getBaseUri(), "backup");
BlobId blobId = BlobId.of(bucketName, sourceDir + "/source.dat");
- realStorage.create(BlobInfo.newBuilder(blobId).build(), data);
+ createBlob(realStorage, blobId, data);
Storage zeroReturningStorage = createZeroReturningStorage(realStorage);
GCSBackupRepository proxyRepo = createRepositoryWithStorage(zeroReturningStorage);
@@ -150,7 +151,7 @@ public class GCSBackupRepositoryTest extends AbstractBackupRepositoryTest {
GCSBackupRepository repo = createRepositoryWithStorage(realStorage);
URI sourceDir = repo.resolve(getBaseUri(), "backup");
BlobId blobId = BlobId.of(bucketName, sourceDir + "/source.dat");
- realStorage.create(BlobInfo.newBuilder(blobId).build(), data);
+ createBlob(realStorage, blobId, data);
try (Directory dest = new ByteBuffersDirectory()) {
repo.copyIndexFileTo(sourceDir, "source.dat", dest, "dest.dat");
@@ -163,6 +164,27 @@ public class GCSBackupRepositoryTest extends AbstractBackupRepositoryTest {
}
}
+ /**
+ * Creates a blob, skipping (rather than failing) the test if the current default locale trips the
+ * known FakeStorageRpc/RFC3339 date-parsing bug - see {@link
+ * LocalStorageGCSBackupRepository#initializeBackupLocation()} for the same pattern.
+ */
+ private static void createBlob(Storage storage, BlobId blobId, byte[] data) {
+ try {
+ storage.create(BlobInfo.newBuilder(blobId).build(), data);
+ } catch (Exception e) {
+ final Throwable cause = e.getCause();
+ Assume.assumeFalse(
+ "This test uses a GCS mock library that is incompatible with the current default locale",
+ cause != null
+ && e instanceof StorageException
+ && cause.getMessage().contains("Invalid date/time format")
+ && cause instanceof NumberFormatException);
+ // Not the known locale incompatibility - a genuine failure, so don't swallow it.
+ throw new RuntimeException(e);
+ }
+ }
+
/** Storage proxy that fails on {@code reader} so we can assert copy errors are propagated. */
private static Storage createFailingStorage() {
Storage delegate = LocalStorageHelper.customOptions(false).getService();
diff --git a/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/LocalStorageGCSBackupRepository.java b/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/LocalStorageGCSBackupRepository.java
index fc26a188f87..da54e135de4 100644
--- a/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/LocalStorageGCSBackupRepository.java
+++ b/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/LocalStorageGCSBackupRepository.java
@@ -96,13 +96,14 @@ public class LocalStorageGCSBackupRepository extends GCSBackupRepository {
createDirectory(baseLocationUri);
} catch (Exception e) {
final Throwable cause = e.getCause();
- if (cause != null) {
- assumeFalse(
- "This test uses a GCS mock library that is incompatible with the current default locale",
- e instanceof StorageException
- && cause.getMessage().contains("Invalid date/time format")
- && cause instanceof NumberFormatException);
- }
+ assumeFalse(
+ "This test uses a GCS mock library that is incompatible with the current default locale",
+ cause != null
+ && e instanceof StorageException
+ && cause.getMessage().contains("Invalid date/time format")
+ && cause instanceof NumberFormatException);
+ // Not the known locale incompatibility - a genuine failure, so don't swallow it.
+ throw new RuntimeException(e);
}
}
}