hudi-agent commented on code in PR #19970:
URL: https://github.com/apache/hudi/pull/19970#discussion_r4043944064
##########
hudi-cli/src/main/java/org/apache/hudi/cli/commands/SparkMain.java:
##########
@@ -97,17 +101,45 @@ public class SparkMain {
* Commands.
*/
enum SparkCommand {
- BOOTSTRAP(21), ROLLBACK(6), DEDUPLICATE(8), ROLLBACK_TO_SAVEPOINT(6),
SAVEPOINT(7),
- IMPORT(13), UPSERT(13), COMPACT_SCHEDULE(6), COMPACT_RUN(10),
COMPACT_SCHEDULE_AND_EXECUTE(9),
- COMPACT_UNSCHEDULE_PLAN(9), COMPACT_UNSCHEDULE_FILE(10),
COMPACT_VALIDATE(7), COMPACT_REPAIR(8),
- CLUSTERING_SCHEDULE(6), CLUSTERING_RUN(9),
CLUSTERING_SCHEDULE_AND_EXECUTE(8), CLEAN(5),
- DELETE_MARKER(5), DELETE_SAVEPOINT(5), UPGRADE(5), DOWNGRADE(5),
- REPAIR_DEPRECATED_PARTITION(4), RENAME_PARTITION(6), ARCHIVE(8);
+ BOOTSTRAP(21, "tableName", "tableType", "targetPath", "srcPath",
"rowKeyField", "partitionPathField",
Review Comment:
🤖 nit: `paramNames` here has to stay in the exact same order as the
positional args each command's `main()` branch expects to unpack. Now that
order is duplicated in two places (this enum and the positional-parsing code
below); might be worth a comment on `paramNames` calling out that ordering is
load-bearing, so a future edit to one side doesn't silently break the other.
<sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag
quality.</i></sub>
##########
hudi-cli/src/main/java/org/apache/hudi/cli/commands/SparkMain.java:
##########
@@ -139,8 +171,96 @@ public static void addAppArgs(SparkLauncher sparkLauncher,
SparkMain.SparkComman
sparkLauncher.addAppArgs(args);
}
- public static void main(String[] args) {
- ValidationUtils.checkArgument(args.length >= 4);
+ /**
+ * Adds the command invocation as fully named options: {@code --command X
--master Y --memory Z}
+ * plus {@code -Dkey=value} pairs for every command argument. This makes the
launcher command
+ * self-describing and removes the risk of mis-aligned positional arguments
at call sites.
+ *
+ * @param namedArgs flat name/value pairs; {@code null} values are skipped,
preserving the
+ * legacy semantics of absent trailing arguments (e.g.
propsFilePath)
+ */
+ public static void addNamedAppArgs(SparkLauncher sparkLauncher,
SparkMain.SparkCommand cmd, String master, String memory, String... namedArgs) {
+ ValidationUtils.checkArgument(namedArgs.length % 2 == 0, "namedArgs must
be flat name/value pairs");
+ sparkLauncher.addAppArgs("--command", cmd.toString());
+ sparkLauncher.addAppArgs("--master", master);
+ sparkLauncher.addAppArgs("--memory", memory);
+ for (int i = 0; i < namedArgs.length; i += 2) {
+ if (namedArgs[i + 1] != null) {
+ sparkLauncher.addAppArgs("-D" + namedArgs[i] + "=" + namedArgs[i + 1]);
+ }
+ }
+ }
+
+ private static boolean isNamedInvocation(String[] argv) {
+ return argv.length > 0 && argv[0].startsWith("-");
+ }
+
+ /**
+ * Translates {@code --command X --master Y --memory Z -Dkey=value...} into
the positional
+ * form {@code [X, Y, Z, arg1, arg2, ...]} expected by the legacy invocation
path. The named
+ * -D arguments are mapped onto the command's positional argument names;
unknown -D keys are
+ * passed through as trailing configs, matching {@link
SparkCommand#makeConfigs(String[])}.
+ */
+ private static String[] namedArgsToPositional(String[] argv) {
+ NamedArgs namedArgs = new NamedArgs();
+ JCommander commander = JCommander.newBuilder()
+ .addObject(namedArgs)
+ .build();
+ commander.parse(argv);
+
+ SparkCommand command = SparkCommand.valueOf(namedArgs.command);
+ Map<String, String> params = namedArgs.params;
+ List<String> positionalArgs = new ArrayList<>(command.paramNames.length);
+ for (String paramName : command.paramNames) {
+ String value = params.get(paramName);
+ // absent named args map to empty strings to keep the positional slots
aligned; trailing
+ // empty slots are handled by getPropsFilePath/makeConfigs exactly like
the legacy form
+ positionalArgs.add(value != null ? value.trim() : "");
+ }
Review Comment:
🤖 nit: `namedArgsToPositional` is doing quite a bit (JCommander parsing,
positional mapping, extra-config passthrough) in one method — might be worth
splitting the "map named params onto positional slots" logic from the "collect
extra -D configs" logic into separate helper methods for readability.
<sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag
quality.</i></sub>
##########
hudi-cli/pom.xml:
##########
@@ -236,6 +236,12 @@
<version>${project.version}</version>
</dependency>
+ <!-- JCommander for SparkMain named-argument CLI (HUDI-96) -->
+ <dependency>
+ <groupId>com.beust</groupId>
+ <artifactId>jcommander</artifactId>
Review Comment:
🤖 Confirmed. The only other jar on the CLI classpath per
`hudi-cli-with-bundle.sh` is `hudi-spark-bundle`, and it relocates jcommander
to `org.apache.hudi.com.beust.jcommander`, so it can't satisfy `SparkMain`'s
unrelocated `com.beust.jcommander` imports either. Since all 12 call sites now
go through `addNamedAppArgs`, this would be a `NoClassDefFoundError` on every
Spark-launching CLI command from the bundle, not just the new named path.
##########
hudi-cli/src/main/java/org/apache/hudi/cli/commands/SparkMain.java:
##########
@@ -139,8 +171,96 @@ public static void addAppArgs(SparkLauncher sparkLauncher,
SparkMain.SparkComman
sparkLauncher.addAppArgs(args);
}
- public static void main(String[] args) {
- ValidationUtils.checkArgument(args.length >= 4);
+ /**
+ * Adds the command invocation as fully named options: {@code --command X
--master Y --memory Z}
+ * plus {@code -Dkey=value} pairs for every command argument. This makes the
launcher command
+ * self-describing and removes the risk of mis-aligned positional arguments
at call sites.
+ *
+ * @param namedArgs flat name/value pairs; {@code null} values are skipped,
preserving the
+ * legacy semantics of absent trailing arguments (e.g.
propsFilePath)
+ */
+ public static void addNamedAppArgs(SparkLauncher sparkLauncher,
SparkMain.SparkCommand cmd, String master, String memory, String... namedArgs) {
+ ValidationUtils.checkArgument(namedArgs.length % 2 == 0, "namedArgs must
be flat name/value pairs");
+ sparkLauncher.addAppArgs("--command", cmd.toString());
+ sparkLauncher.addAppArgs("--master", master);
+ sparkLauncher.addAppArgs("--memory", memory);
+ for (int i = 0; i < namedArgs.length; i += 2) {
+ if (namedArgs[i + 1] != null) {
+ sparkLauncher.addAppArgs("-D" + namedArgs[i] + "=" + namedArgs[i + 1]);
+ }
+ }
+ }
+
+ private static boolean isNamedInvocation(String[] argv) {
+ return argv.length > 0 && argv[0].startsWith("-");
+ }
+
+ /**
+ * Translates {@code --command X --master Y --memory Z -Dkey=value...} into
the positional
+ * form {@code [X, Y, Z, arg1, arg2, ...]} expected by the legacy invocation
path. The named
+ * -D arguments are mapped onto the command's positional argument names;
unknown -D keys are
+ * passed through as trailing configs, matching {@link
SparkCommand#makeConfigs(String[])}.
+ */
+ private static String[] namedArgsToPositional(String[] argv) {
+ NamedArgs namedArgs = new NamedArgs();
+ JCommander commander = JCommander.newBuilder()
+ .addObject(namedArgs)
+ .build();
+ commander.parse(argv);
+
+ SparkCommand command = SparkCommand.valueOf(namedArgs.command);
+ Map<String, String> params = namedArgs.params;
+ List<String> positionalArgs = new ArrayList<>(command.paramNames.length);
+ for (String paramName : command.paramNames) {
+ String value = params.get(paramName);
+ // absent named args map to empty strings to keep the positional slots
aligned; trailing
+ // empty slots are handled by getPropsFilePath/makeConfigs exactly like
the legacy form
+ positionalArgs.add(value != null ? value.trim() : "");
+ }
+ // extra -D keys are forwarded as trailing configs, mirroring makeConfigs
behavior
+ List<String> extraConfigs = new ArrayList<>();
+ for (Map.Entry<String, String> entry : params.entrySet()) {
+ if (!Arrays.asList(command.paramNames).contains(entry.getKey())) {
+ extraConfigs.add(entry.getKey() + "=" + entry.getValue());
+ }
+ }
+
+ List<String> positional = new ArrayList<>();
+ positional.add(namedArgs.command);
+ positional.add(namedArgs.master);
+ positional.add(namedArgs.memory);
+ positional.addAll(positionalArgs);
+ positional.addAll(extraConfigs);
+ return positional.toArray(new String[0]);
+ }
+
+ /**
+ * Named-argument form of a SparkMain invocation. Kept separate from the
legacy
+ * positional protocol so that existing call sites and scripts keep working
unchanged:
+ * if the first argument does not look like a named option the legacy path
is used.
+ */
+ private static class NamedArgs {
+ @Parameter(names = {"--command", "-command"}, description = "Name of the
Spark command to run (e.g. ROLLBACK)", required = true)
+ private String command;
+
+ @Parameter(names = {"--master", "-master"}, description = "Spark master
URL (was positional arg 2)", required = true)
+ private String master;
+
+ @Parameter(names = {"--memory", "-memory"}, description = "Spark driver
memory (was positional arg 3)", required = true)
+ private String memory;
+
+ @DynamicParameter(names = "-D", description = "Command arguments and spark
configs as -Dkey=value pairs")
+ private Map<String, String> params = new HashMap<>();
Review Comment:
🤖 Confirmed — `validateAndAddProperties` appends bare `key=value` tokens
after the `--command/--master/--memory/-D...` args, and JCommander with no main
parameter rejects them. One alternative to adding a main `@Parameter`: have
those call sites route the configs through `addNamedAppArgs` as extra
name/value pairs, since `namedArgsToPositional` already forwards any `-D` key
not in `paramNames` to the trailing-config tail that `makeConfigs` consumes.
That keeps a single parsing path rather than two ways of expressing trailing
configs.
##########
hudi-cli/src/main/java/org/apache/hudi/cli/commands/SparkMain.java:
##########
@@ -139,8 +171,96 @@ public static void addAppArgs(SparkLauncher sparkLauncher,
SparkMain.SparkComman
sparkLauncher.addAppArgs(args);
}
- public static void main(String[] args) {
- ValidationUtils.checkArgument(args.length >= 4);
+ /**
+ * Adds the command invocation as fully named options: {@code --command X
--master Y --memory Z}
+ * plus {@code -Dkey=value} pairs for every command argument. This makes the
launcher command
+ * self-describing and removes the risk of mis-aligned positional arguments
at call sites.
+ *
+ * @param namedArgs flat name/value pairs; {@code null} values are skipped,
preserving the
+ * legacy semantics of absent trailing arguments (e.g.
propsFilePath)
+ */
+ public static void addNamedAppArgs(SparkLauncher sparkLauncher,
SparkMain.SparkCommand cmd, String master, String memory, String... namedArgs) {
+ ValidationUtils.checkArgument(namedArgs.length % 2 == 0, "namedArgs must
be flat name/value pairs");
Review Comment:
🤖 Agree, and there's a second failure mode here: because any `-D` key not in
`paramNames` is silently forwarded as a trailing config, a typo like
`-DbasePth=/x` leaves the `basePath` slot empty *and* passes `assertGtEq` for
commands like CLEAN/COMPACT_RUN — it just shows up as a bogus hoodie config.
Validating supplied names in `addNamedAppArgs` would catch this on the CLI side
before the Spark app is even launched.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]