Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog/unreleased/SOLR-17697-picocli-experimental-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ links:
url: https://issues.apache.org/jira/browse/SOLR-18511
- name: SOLR-18512
url: https://issues.apache.org/jira/browse/SOLR-18512
- name: SOLR-18513
url: https://issues.apache.org/jira/browse/SOLR-18513
136 changes: 135 additions & 1 deletion solr/core/src/java/org/apache/solr/cli/ExportTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,22 @@
import org.noggit.JSONWriter;

/** Supports export command in the bin/solr script. */
@SuppressWarnings("UnnecessarilyFullyQualified")
@picocli.CommandLine.Command(
name = "export",
description = "Exports documents from a collection to a local file.",
footerHeading = "%nExamples:%n",
footer = {
" # Export a collection's documents to gettingstarted.json",
" bin/solr export -c gettingstarted --solr-url http://localhost:8983"
})
public class ExportTool extends ToolBase {

/**
* @deprecated Only used by the commons-cli parser; the picocli path declares this as an annotated
* field.
*/
@Deprecated
private static final Option COLLECTION_NAME_OPTION =
Option.builder("c")
.longOpt("name")
Expand All @@ -94,6 +108,11 @@ public class ExportTool extends ToolBase {
.desc("Name of the collection.")
.get();

/**
* @deprecated Only used by the commons-cli parser; the picocli path declares this as an annotated
* field.
*/
@Deprecated
private static final Option OUTPUT_OPTION =
Option.builder()
.longOpt("output")
Expand All @@ -103,6 +122,11 @@ public class ExportTool extends ToolBase {
"Path to output the exported data, and optionally the file name, defaults to 'collection-name'.")
.get();

/**
* @deprecated Only used by the commons-cli parser; the picocli path declares this as an annotated
* field.
*/
@Deprecated
private static final Option FORMAT_OPTION =
Option.builder()
.longOpt("format")
Expand All @@ -111,9 +135,19 @@ public class ExportTool extends ToolBase {
.desc("Output format for exported docs (json, jsonl or javabin), defaulting to json.")
.get();

/**
* @deprecated Only used by the commons-cli parser; the picocli path declares this as an annotated
* field.
*/
@Deprecated
private static final Option COMPRESS_OPTION =
Option.builder().longOpt("compress").desc("Compress the output. Defaults to false.").get();

/**
* @deprecated Only used by the commons-cli parser; the picocli path declares this as an annotated
* field.
*/
@Deprecated
private static final Option LIMIT_OPTION =
Option.builder()
.longOpt("limit")
Expand All @@ -122,6 +156,11 @@ public class ExportTool extends ToolBase {
.desc("Maximum number of docs to download. Default is 100, use -1 for all docs.")
.get();

/**
* @deprecated Only used by the commons-cli parser; the picocli path declares this as an annotated
* field.
*/
@Deprecated
private static final Option QUERY_OPTION =
Option.builder()
.longOpt("query")
Expand All @@ -130,6 +169,11 @@ public class ExportTool extends ToolBase {
.desc("A custom query, default is '*:*'.")
.get();

/**
* @deprecated Only used by the commons-cli parser; the picocli path declares this as an annotated
* field.
*/
@Deprecated
private static final Option FIELDS_OPTION =
Option.builder()
.longOpt("fields")
Expand All @@ -149,6 +193,74 @@ record ExportParams(
String fields,
String limit) {}

// --- picocli fields ---
// The connection group is mandatory (multiplicity "1"): the commons-cli path throws
// IllegalArgumentException when no connection target is given, so ArgGroup enforces the same
// requirement declaratively.

@picocli.CommandLine.ArgGroup(exclusive = true, multiplicity = "1")
private ConnectionOptions connectionOptions;

@picocli.CommandLine.Mixin private CredentialsOptions credentialsOptions;

@picocli.CommandLine.Option(
names = {"-c", "--name"},
required = true,
paramLabel = "NAME",
description = "Name of the collection.")
private String nameOpt;

@picocli.CommandLine.Option(
names = "--output",
paramLabel = "PATH",
description =
"Path to output the exported data, and optionally the file name, defaults to"
+ " 'collection-name'.")
private String outputOpt;

/** The values of {@code --format}; each constant is spelled as it is on the command line. */
enum Format {
json,
jsonl,
javabin
}

@picocli.CommandLine.Option(
names = "--format",
paramLabel = "FORMAT",
description =
"Output format for exported docs (${COMPLETION-CANDIDATES}), defaulting to json.")
private Format formatOpt;

@picocli.CommandLine.Option(
names = "--compress",
description = "Compress the output. Defaults to false.")
private boolean compressOpt;

@picocli.CommandLine.Option(
names = "--limit",
Comment thread
janhoy marked this conversation as resolved.
defaultValue = "100",
paramLabel = "#",
description = "Maximum number of docs to download. Default is 100, use -1 for all docs.")
private long limitOpt;

@picocli.CommandLine.Option(
names = "--query",
defaultValue = "*:*",
paramLabel = "QUERY",
description = "A custom query, default is '*:*'.")
private String queryOpt;

@picocli.CommandLine.Option(
names = "--fields",
paramLabel = "FIELDA,FIELDB",
description = "Comma separated list of fields to export. By default all fields are fetched.")
private String fieldsOpt;

public ExportTool() {
this(new DefaultToolRuntime());
}

public ExportTool(ToolRuntime runtime) {
super(runtime);
}
Expand Down Expand Up @@ -729,6 +841,28 @@ static long getDocCount(String coreName, SolrClient client, String query)

@Override
public int callTool() throws Exception {
throw new UnsupportedOperationException("This tool does not yet support PicoCli");
String url = resolveSolrUrl(credentialsOptions.credentials) + "/solr/" + nameOpt;
ExportParams params =
new ExportParams(
url,
credentialsOptions.credentials,
queryOpt,
outputOpt,
formatOpt == null ? null : formatOpt.name(),
compressOpt,
fieldsOpt,
String.valueOf(limitOpt));
export(params);
return 0;
}

private String resolveSolrUrl(String credentials) throws Exception {
String solrUrlArg = connectionOptions.effectiveSolrUrl();
if (solrUrlArg != null) {
return CLIUtils.normalizeSolrUrl(solrUrlArg);
}
return CLIUtils.solrUrlFromConnection(
CloudSolrClient.CloudSolrClientConnection.parse(connectionOptions.effectiveZkHost()),
credentials);
}
}
3 changes: 2 additions & 1 deletion solr/core/src/java/org/apache/solr/cli/SolrCLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@
AssertTool.class,
ApiTool.class,
ClusterTool.class,
ConfigTool.class
ConfigTool.class,
ExportTool.class
})
public class SolrCLI implements CLIO, java.util.concurrent.Callable<Integer> {

Expand Down
46 changes: 46 additions & 0 deletions solr/core/src/test/org/apache/solr/cli/ExportToolPicocliTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 java.util.Arrays;
import org.junit.Test;
import picocli.CommandLine;

/**
* Runs all {@link ExportToolTest} tests through the picocli invocation path.
*
* <p>All {@code @Test} methods are inherited; only the invocation strategy is overridden.
*/
public class ExportToolPicocliTest extends ExportToolTest {

@Override
protected int runTool(String[] args, Class<? extends ToolBase> clazz) throws Exception {
// args[0] is the tool name used by commons-cli dispatch; strip it for picocli.
String[] toolArgs = Arrays.copyOfRange(args, 1, args.length);
ToolRuntime runtime = new CLITestHelper.TestingRuntime(false);
ToolBase tool = clazz.getDeclaredConstructor(ToolRuntime.class).newInstance(runtime);
return new CommandLine(tool)
.setDefaultValueProvider(new CliDefaultValueProvider())
.execute(toolArgs);
}

@Test
public void testNonNumericLimitIsAUsageError() throws Exception {
// picocli reads --limit as a number, so it rejects "abc" before the tool runs: usage error 2
assertEquals(2, exportTo(createTempDir(), "--limit", "abc"));
}
}
Loading
Loading