This is an automated email from the ASF dual-hosted git repository.

isapego pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new ab17e85d5e IGNITE-18101: Add help for SQL commands (#1362)
ab17e85d5e is described below

commit ab17e85d5ee2617ddea54fafbfb97564d66350ad
Author: Ivan Gagarkin <[email protected]>
AuthorDate: Thu Dec 8 14:05:26 2022 +0400

    IGNITE-18101: Add help for SQL commands (#1362)
    
    Co-authored-by: Ivan Gagarkin <[email protected]>
    Co-authored-by: Aleksandr Pakhomov <[email protected]>
---
 ...eplTopLevelCliCommand.java => ExitCommand.java} | 25 +++---
 .../cli/commands/TopLevelCliReplCommand.java       |  1 +
 .../internal/cli/commands/sql/SqlCompleter.java    |  3 +
 .../internal/cli/commands/sql/SqlReplCommand.java  |  6 +-
 .../commands/sql/SqlReplTopLevelCliCommand.java    | 17 ++--
 .../cli/commands/sql/help/IgniteSqlCommand.java    | 99 ++++++++++++++++++++++
 .../sql/help/IgniteSqlCommandCompleter.java        | 44 ++++++++++
 .../cli/commands/sql/help/SqlHelpCommand.java      | 93 ++++++++++++++++++++
 .../handler/PicocliExecutionExceptionHandler.java  | 10 ++-
 .../core/repl/executor/IgnitePicocliCommands.java  | 13 ++-
 .../repl/executor/RegistryCommandExecutor.java     | 32 +++----
 .../cli/core/repl/executor/ReplExecutor.java       | 45 +++++-----
 .../cli/commands/sql/help/SqlHelpCommandTest.java  | 65 ++++++++++++++
 13 files changed, 384 insertions(+), 69 deletions(-)

diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplTopLevelCliCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/ExitCommand.java
similarity index 63%
copy from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplTopLevelCliCommand.java
copy to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/ExitCommand.java
index 5993cd1006..0004139d19 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplTopLevelCliCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/ExitCommand.java
@@ -15,22 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.cli.commands.sql;
+package org.apache.ignite.internal.cli.commands;
 
-import jakarta.inject.Singleton;
-import picocli.CommandLine;
-import picocli.shell.jline3.PicocliCommands;
+import org.jline.reader.EndOfFileException;
+import picocli.CommandLine.Command;
 
 /**
- * Top level SQL REPL command.
+ * Command to exit from app.
  */
[email protected](name = "",
-        description = {""},
-        footer = {"", "Press Ctrl-D to exit"},
-        subcommands = {
-            CommandLine.HelpCommand.class,
-            PicocliCommands.ClearScreen.class
-        })
-@Singleton
-public class SqlReplTopLevelCliCommand {
+@Command(name = "exit", description = "Exit")
+public class ExitCommand extends BaseCommand implements Runnable {
+
+    @Override
+    public void run() {
+        throw new EndOfFileException();
+    }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliReplCommand.java
index 0f801a1c49..53c99a3c9c 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliReplCommand.java
@@ -36,6 +36,7 @@ import picocli.shell.jline3.PicocliCommands;
                 SqlReplCommand.class,
                 PicocliCommands.ClearScreen.class,
                 CommandLine.HelpCommand.class,
+                ExitCommand.class,
                 VersionCommand.class,
                 CliReplCommand.class,
                 ConnectReplCommand.class,
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCompleter.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCompleter.java
index c17ba58c8a..57f960d8a6 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCompleter.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCompleter.java
@@ -33,6 +33,9 @@ class SqlCompleter implements Completer {
 
     @Override
     public void complete(LineReader reader, ParsedLine commandLine, 
List<Candidate> candidates) {
+        if (commandLine.line().startsWith("help")) {
+            return;
+        }
         if (commandLine.wordIndex() == 0) {
             addCandidatesFromArray(SqlMetaData.STARTING_KEYWORDS, candidates);
         } else {
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
index 3ce9e32ce8..71cf1948b4 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
@@ -35,6 +35,7 @@ import java.nio.file.Files;
 import java.sql.SQLException;
 import org.apache.ignite.internal.cli.call.sql.SqlQueryCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
+import 
org.apache.ignite.internal.cli.commands.sql.help.IgniteSqlCommandCompleter;
 import org.apache.ignite.internal.cli.core.CallExecutionPipelineProvider;
 import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.internal.cli.core.call.StringCallInput;
@@ -51,6 +52,7 @@ import 
org.apache.ignite.internal.cli.decorators.SqlQueryResultDecorator;
 import org.apache.ignite.internal.cli.decorators.TableDecorator;
 import org.apache.ignite.internal.cli.sql.SqlManager;
 import org.apache.ignite.internal.cli.sql.SqlSchemaProvider;
+import org.jline.reader.impl.completer.AggregateCompleter;
 import picocli.CommandLine.ArgGroup;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
@@ -98,9 +100,11 @@ public class SqlReplCommand extends BaseCommand implements 
Runnable {
         try (SqlManager sqlManager = new SqlManager(jdbc)) {
             // When passing white space to this command, picocli will treat it 
as a positional argument
             if (execOptions == null || (execOptions.command != null && 
execOptions.command.isBlank())) {
+                SqlCompleter sqlCompleter = new SqlCompleter(new 
SqlSchemaProvider(sqlManager::getMetadata));
+                IgniteSqlCommandCompleter sqlCommandCompleter = new 
IgniteSqlCommandCompleter();
                 replExecutorProvider.get().execute(Repl.builder()
                         .withPromptProvider(() -> 
ansi(fg(Color.GREEN).mark("sql-cli> ")))
-                        .withCompleter(new SqlCompleter(new 
SqlSchemaProvider(sqlManager::getMetadata)))
+                        .withCompleter(new 
AggregateCompleter(sqlCommandCompleter, sqlCompleter))
                         .withCommandClass(SqlReplTopLevelCliCommand.class)
                         
.withCallExecutionPipelineProvider(provider(sqlManager))
                         .withHistoryFileName("sqlhistory")
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplTopLevelCliCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplTopLevelCliCommand.java
index 5993cd1006..0bbbbe401c 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplTopLevelCliCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplTopLevelCliCommand.java
@@ -18,19 +18,22 @@
 package org.apache.ignite.internal.cli.commands.sql;
 
 import jakarta.inject.Singleton;
-import picocli.CommandLine;
-import picocli.shell.jline3.PicocliCommands;
+import org.apache.ignite.internal.cli.commands.ExitCommand;
+import org.apache.ignite.internal.cli.commands.sql.help.SqlHelpCommand;
+import picocli.CommandLine.Command;
+import picocli.shell.jline3.PicocliCommands.ClearScreen;
 
 /**
  * Top level SQL REPL command.
  */
[email protected](name = "",
+@Command(name = "",
         description = {""},
-        footer = {"", "Press Ctrl-D to exit"},
         subcommands = {
-            CommandLine.HelpCommand.class,
-            PicocliCommands.ClearScreen.class
-        })
+                ClearScreen.class,
+                ExitCommand.class,
+                SqlHelpCommand.class
+        }
+)
 @Singleton
 public class SqlReplTopLevelCliCommand {
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/help/IgniteSqlCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/help/IgniteSqlCommand.java
new file mode 100644
index 0000000000..a52863053c
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/help/IgniteSqlCommand.java
@@ -0,0 +1,99 @@
+/*
+ * 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.ignite.internal.cli.commands.sql.help;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+/** Enum of Ignite SQL commands. */
+public enum IgniteSqlCommand {
+    SELECT("SELECT",
+            "SELECT [ hintComment ] [ STREAM ] [ ALL | DISTINCT ]\n"
+                    + "    { * | projectItem [, projectItem ]* }\n"
+                    + "FROM tableExpression\n"
+                    + "[ WHERE booleanExpression ]\n"
+                    + "[ GROUP BY { groupItem [, groupItem ]* } ]\n"
+                    + "[ HAVING booleanExpression ]"),
+    select("select", SELECT.syntax),
+
+    INSERT("INSERT",
+            "INSERT INTO tableName\n"
+                    + "[ '(' column [, column ]* ')' ]"),
+    insert("insert", INSERT.syntax),
+
+    UPDATE("UPDATE",
+            "UPDATE tableName\n"
+                    + "SET assign [, assign ]*\n"
+                    + "[ WHERE booleanExpression ]"),
+    update("update", UPDATE.syntax),
+
+    DELETE("DELETE",
+            "DELETE FROM tableName [ [ AS ] alias ]\n"
+                    + "[ WHERE booleanExpression ]"),
+    delete("delete", DELETE.syntax),
+
+    CREATE_TABLE("CREATE TABLE",
+            "CREATE TABLE [IF NOT EXISTS] tableName (tableColumn [, 
tableColumn]...)\n"
+                    + "[COLOCATE [BY] (columnName [, columnName]...)]\n"
+                    + "[ENGINE engineName]\n"
+                    + "[WITH paramName=paramValue 
[,paramName=paramValue]...]\n"
+                    + "tableColumn = columnName columnType [[NOT] NULL] 
[DEFAULT defaultValue] [PRIMARY KEY]"),
+    create_table("create table", CREATE_TABLE.syntax),
+
+    DROP_TABLE("DROP TABLE",
+            "DROP TABLE IF EXISTS tableName"),
+    drop_table("drop table", DROP_TABLE.syntax),
+
+    ALTER_TABLE("ALTER TABLE",
+            "ALTER TABLE tableName ADD COLUMN (tableColumn [, tableColumn]...) 
\n\n"
+                    + "ALTER TABLE tableName DROP COLUMN (tableColumn [, 
tableColumn]...)"),
+    alter_table("alter table", ALTER_TABLE.syntax),
+
+    CREATE_INDEX("CREATE INDEX",
+            "CREATE INDEX [IF NOT EXISTS] name ON tableName\n"
+                    + "[USING { HASH | TREE }]\n"
+                    + "(column_definition [, column_definition]...)"),
+    create_index("create index", CREATE_INDEX.syntax),
+
+    DROP_INDEX("DROP INDEX",
+            "DROP INDEX [IF EXISTS] indexName"),
+    drop_index("drop index", DROP_INDEX.syntax);
+
+    private final String topic;
+    private final String syntax;
+
+    IgniteSqlCommand(String topic, String syntax) {
+        this.topic = topic;
+        this.syntax = syntax;
+    }
+
+    /** Finds a SQL command by a topic. */
+    static Optional<IgniteSqlCommand> find(String topic) {
+        return Arrays.stream(values())
+                .filter(it -> it.getTopic().equalsIgnoreCase(topic))
+                .findFirst();
+    }
+
+    String getTopic() {
+        return topic;
+    }
+
+    String getSyntax() {
+        return syntax;
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/help/IgniteSqlCommandCompleter.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/help/IgniteSqlCommandCompleter.java
new file mode 100644
index 0000000000..37b916c119
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/help/IgniteSqlCommandCompleter.java
@@ -0,0 +1,44 @@
+/*
+ * 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.ignite.internal.cli.commands.sql.help;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.jline.reader.Candidate;
+import org.jline.reader.Completer;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+
+/** Completer for Ignite SQL commands. */
+public class IgniteSqlCommandCompleter implements Completer {
+    @Override
+    public void complete(LineReader reader, ParsedLine line, List<Candidate> 
candidates) {
+        if (line.line().startsWith("help")) {
+            String args = line.words()
+                    .stream()
+                    .skip(1)
+                    .collect(Collectors.joining(" "));
+            Arrays.stream(IgniteSqlCommand.values())
+                    .map(IgniteSqlCommand::getTopic)
+                    .filter(topic -> topic.startsWith(args))
+                    .map(Candidate::new)
+                    .forEach(candidates::add);
+        }
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/help/SqlHelpCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/help/SqlHelpCommand.java
new file mode 100644
index 0000000000..a62be2b8fc
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/help/SqlHelpCommand.java
@@ -0,0 +1,93 @@
+/*
+ * 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.ignite.internal.cli.commands.sql.help;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.StringJoiner;
+import org.apache.ignite.internal.cli.core.exception.IgniteCliException;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Help.ColorScheme;
+import picocli.CommandLine.IHelpCommandInitializable2;
+import picocli.CommandLine.Parameters;
+
+/** Help command in SQL repl mode. */
+@Command(name = "help",
+        header = "Display help information about the specified SQL command.",
+        synopsisHeading = "%nUsage: ",
+        helpCommand = true,
+        description = {
+                "%nWhen no SQL command is given, the usage help for the main 
command is displayed.",
+                "If a SQL command is specified, the help for that command is 
shown.%n"}
+)
+public final class SqlHelpCommand implements IHelpCommandInitializable2, 
Runnable {
+
+    @Parameters(paramLabel = "Ignite SQL command",
+            arity = "0..2",
+            description = "The SQL command to display the usage help message 
for.")
+    private String[] parameters;
+
+    private CommandLine self;
+
+    private PrintWriter outWriter;
+
+    private ColorScheme colorScheme;
+
+    @Override
+    public void run() {
+        if (parameters != null) {
+            String command = String.join(" ", this.parameters);
+            String commandUsage = IgniteSqlCommand.find(command)
+                    .map(IgniteSqlCommand::getSyntax)
+                    .or(() -> {
+                        return Optional.ofNullable(self.getParent())
+                                .map(it -> 
it.getSubcommands().get(command).getUsageMessage());
+                    })
+                    .orElseThrow(() -> new IgniteCliException("Unknown 
command: " + command));
+            outWriter.println(commandUsage);
+        } else {
+            String helpMessage = self.getParent().getUsageMessage(colorScheme)
+                    + System.lineSeparator()
+                    + sqlCommands()
+                    + System.lineSeparator()
+                    + System.lineSeparator()
+                    + "\nPress Ctrl-D to exit";
+            outWriter.println(helpMessage);
+        }
+    }
+
+    private static String sqlCommands() {
+        StringJoiner joiner = new StringJoiner(System.lineSeparator());
+        joiner.add("SQL commands: ");
+        Arrays.stream(IgniteSqlCommand.values())
+                .map(IgniteSqlCommand::getTopic)
+                .forEach(joiner::add);
+        return joiner.toString();
+    }
+
+    @Override
+    public void init(CommandLine helpCommandLine, ColorScheme colorScheme, 
PrintWriter out, PrintWriter err) {
+        this.self = Objects.requireNonNull(helpCommandLine, "helpCommandLine");
+        this.colorScheme = Objects.requireNonNull(colorScheme, "colorScheme");
+        this.outWriter = Objects.requireNonNull(out, "outWriter");
+        Objects.requireNonNull(err, "errWriter");
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/PicocliExecutionExceptionHandler.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/PicocliExecutionExceptionHandler.java
index f8ec61ee0e..f4949ad3c4 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/PicocliExecutionExceptionHandler.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/PicocliExecutionExceptionHandler.java
@@ -26,7 +26,15 @@ import picocli.CommandLine.ParseResult;
  * Implementation of {@link IExecutionExceptionHandler} based on {@link 
ExceptionHandlers}.
  */
 public class PicocliExecutionExceptionHandler implements 
IExecutionExceptionHandler {
-    private final ExceptionHandlers exceptionHandlers = new 
DefaultExceptionHandlers();
+    private final ExceptionHandlers exceptionHandlers;
+
+    public PicocliExecutionExceptionHandler() {
+        exceptionHandlers = new DefaultExceptionHandlers();
+    }
+
+    public PicocliExecutionExceptionHandler(ExceptionHandlers 
exceptionHandlers) {
+        this.exceptionHandlers = exceptionHandlers;
+    }
 
     @Override
     public int handleExecutionException(Exception ex, CommandLine commandLine, 
ParseResult parseResult) {
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
index 704f547ab4..75485b2c47 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
@@ -51,6 +51,7 @@ import picocli.shell.jline3.PicocliCommands;
 public class IgnitePicocliCommands implements CommandRegistry {
 
     private final CommandLine cmd;
+
     private final Set<String> commands;
     private final Map<String, String> aliasCommand = new HashMap<>();
     private final DynamicCompleterRegistry completerRegistry;
@@ -73,9 +74,9 @@ public class IgnitePicocliCommands implements CommandRegistry 
{
         }
     }
 
-    /** Returns the usage help message as a String. */
-    public String usageMessage() {
-        return cmd.getUsageMessage();
+    /** Returns the {@link CommandLine} instance. */
+    public CommandLine getCmd() {
+        return cmd;
     }
 
     /** {@inheritDoc} */
@@ -156,7 +157,7 @@ public class IgnitePicocliCommands implements 
CommandRegistry {
     }
 
     @Override
-    public Object invoke(CommandRegistry.CommandSession session, String 
command, Object[] args) throws Exception {
+    public Object invoke(CommandRegistry.CommandSession session, String 
command, Object[] args) {
         List<String> arguments = new ArrayList<>();
         arguments.add(command);
         Arrays.stream(args).map(Object::toString).forEach(arguments::add);
@@ -164,6 +165,10 @@ public class IgnitePicocliCommands implements 
CommandRegistry {
         return null;
     }
 
+    public Object executeHelp(Object[] args) {
+        return invoke(new CommandSession(), "help", args);
+    }
+
     @Override
     public Set<String> commandNames() {
         return commands;
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/RegistryCommandExecutor.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/RegistryCommandExecutor.java
index ba3b0d095f..560e03bdc7 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/RegistryCommandExecutor.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/RegistryCommandExecutor.java
@@ -18,7 +18,7 @@
 package org.apache.ignite.internal.cli.core.repl.executor;
 
 
-import java.util.function.Supplier;
+import java.util.ArrayList;
 import org.apache.ignite.internal.cli.core.call.Call;
 import org.apache.ignite.internal.cli.core.call.CallOutput;
 import org.apache.ignite.internal.cli.core.call.DefaultCallOutput;
@@ -27,28 +27,26 @@ import org.jline.console.SystemRegistry;
 import org.jline.reader.ParsedLine;
 import org.jline.reader.Parser;
 import org.jline.reader.Parser.ParseContext;
+import picocli.CommandLine;
 
 /**
  * Command executor based on {@link SystemRegistry}.
  */
 public class RegistryCommandExecutor implements Call<StringCallInput, Object> {
-    private final SystemRegistry systemRegistry;
 
     private final Parser parser;
 
-    private final Supplier<String> usageMessage;
+    private final CommandLine commandLine;
 
     /**
      * Constructor.
      *
-     * @param systemRegistry {@link SystemRegistry} instance.
      * @param parser A {@link Parser} used to create {@code systemRegistry}.
-     * @param usage A {@link Supplier} used to provide help message.
+     * @param commandLine {@link CommandLine} instance.
      */
-    public RegistryCommandExecutor(SystemRegistry systemRegistry, Parser 
parser, Supplier<String> usage) {
-        this.systemRegistry = systemRegistry;
+    public RegistryCommandExecutor(Parser parser, CommandLine commandLine) {
         this.parser = parser;
-        this.usageMessage = usage;
+        this.commandLine = commandLine;
     }
 
     /**
@@ -60,16 +58,10 @@ public class RegistryCommandExecutor implements 
Call<StringCallInput, Object> {
     @Override
     public CallOutput<Object> execute(StringCallInput input) {
         try {
-            String line = input.getString();
-            if (line.trim().equals("help")) {
-                return DefaultCallOutput.success(usageMessage.get());
-            }
-            Object executionResult = systemRegistry.execute(line);
-            if (executionResult == null) {
-                return DefaultCallOutput.empty();
-            }
-
-            return DefaultCallOutput.success(executionResult);
+            String[] args = new ArrayList<>(parser.parse(input.getString(), 0, 
ParseContext.SPLIT_LINE).words())
+                    .toArray(new String[0]);
+            commandLine.execute(args);
+            return DefaultCallOutput.empty();
         } catch (Exception e) {
             return DefaultCallOutput.failure(e);
         }
@@ -79,7 +71,7 @@ public class RegistryCommandExecutor implements 
Call<StringCallInput, Object> {
      * Clean up {@link SystemRegistry}.
      */
     public void cleanUp() {
-        systemRegistry.cleanUp();
+        commandLine.clearExecutionResults();
     }
 
     /**
@@ -91,6 +83,6 @@ public class RegistryCommandExecutor implements 
Call<StringCallInput, Object> {
     public boolean hasCommand(String line) {
         ParsedLine pl = parser.parse(line, 0, ParseContext.SPLIT_LINE);
 
-        return !pl.words().isEmpty() && 
systemRegistry.hasCommand(parser.getCommand(pl.words().get(0)));
+        return !pl.words().isEmpty() && 
commandLine.getSubcommands().containsKey(parser.getCommand(pl.words().get(0)));
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java
index a8bfeabc8f..e7226aef06 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java
@@ -45,7 +45,6 @@ import org.jline.reader.LineReaderBuilder;
 import org.jline.reader.MaskingCallback;
 import org.jline.reader.Parser;
 import org.jline.reader.impl.DefaultParser;
-import org.jline.reader.impl.completer.AggregateCompleter;
 import org.jline.terminal.Terminal;
 import org.jline.widget.TailTipWidgets;
 import picocli.CommandLine;
@@ -84,6 +83,22 @@ public class ReplExecutor {
         this.nodeNameRegistry = nodeNameRegistry;
     }
 
+    private static TailTipWidgets createWidgets(SystemRegistryImpl registry, 
LineReader reader) {
+        TailTipWidgets widgets = new TailTipWidgets(reader, 
registry::commandDescription, 5,
+                TailTipWidgets.TipType.COMPLETER);
+        widgets.enable();
+        // Workaround for the 
https://issues.apache.org/jira/browse/IGNITE-17346
+        // Turn off tailtip widgets before printing to the output
+        CommandLineContextProvider.setPrintWrapper(printer -> {
+            widgets.disable();
+            printer.run();
+            widgets.enable();
+        });
+        // Workaround for jline issue where TailTipWidgets will produce NPE 
when passed a bracket
+        registry.setScriptDescription(cmdLine -> null);
+        return widgets;
+    }
+
     /**
      * Executor method. This is thread blocking method, until REPL stop 
executing.
      *
@@ -97,14 +112,16 @@ public class ReplExecutor {
             SystemRegistryImpl registry = new SystemRegistryImpl(parser, 
terminal, workDirProvider, null);
             registry.setCommandRegistries(picocliCommands);
 
-            LineReader reader = createReader(repl.getCompleter() != null
-                    ? new AggregateCompleter(registry.completer(), 
repl.getCompleter())
-                    : registry.completer());
+            LineReader reader = createReader(
+                    repl.getCompleter() != null
+                            ? repl.getCompleter()
+                            : registry.completer()
+            );
             if (repl.getHistoryFileName() != null) {
                 reader.variable(LineReader.HISTORY_FILE, 
StateFolderProvider.getStateFile(repl.getHistoryFileName()));
             }
 
-            RegistryCommandExecutor executor = new 
RegistryCommandExecutor(registry, parser, picocliCommands::usageMessage);
+            RegistryCommandExecutor executor = new 
RegistryCommandExecutor(parser, picocliCommands.getCmd());
             TailTipWidgets widgets = repl.isTailTipWidgetsEnabled() ? 
createWidgets(registry, reader) : null;
 
             QuestionAskerFactory.setReadWriter(new 
JlineQuestionWriterReader(reader, widgets));
@@ -131,22 +148,6 @@ public class ReplExecutor {
         }
     }
 
-    private static TailTipWidgets createWidgets(SystemRegistryImpl registry, 
LineReader reader) {
-        TailTipWidgets widgets = new TailTipWidgets(reader, 
registry::commandDescription, 5,
-                TailTipWidgets.TipType.COMPLETER);
-        widgets.enable();
-        // Workaround for the 
https://issues.apache.org/jira/browse/IGNITE-17346
-        // Turn off tailtip widgets before printing to the output
-        CommandLineContextProvider.setPrintWrapper(printer -> {
-            widgets.disable();
-            printer.run();
-            widgets.enable();
-        });
-        // Workaround for jline issue where TailTipWidgets will produce NPE 
when passed a bracket
-        registry.setScriptDescription(cmdLine -> null);
-        return widgets;
-    }
-
     private LineReader createReader(Completer completer) {
         LineReader result = LineReaderBuilder.builder()
                 .terminal(terminal)
@@ -166,7 +167,7 @@ public class ReplExecutor {
             cmd.setDefaultValueProvider(defaultValueProvider);
         }
         CommandLineContextProvider.setCmd(cmd);
-        cmd.setExecutionExceptionHandler(new 
PicocliExecutionExceptionHandler());
+        cmd.setExecutionExceptionHandler(new 
PicocliExecutionExceptionHandler(exceptionHandlers));
         cmd.registerConverter(NodeNameOrUrl.class, new 
NodeNameOrUrlConverter(nodeNameRegistry));
 
         DynamicCompleterRegistry completerRegistry = 
factory.create(DynamicCompleterRegistry.class);
diff --git 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/sql/help/SqlHelpCommandTest.java
 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/sql/help/SqlHelpCommandTest.java
new file mode 100644
index 0000000000..c8fa6e02d7
--- /dev/null
+++ 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/sql/help/SqlHelpCommandTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.ignite.internal.cli.commands.sql.help;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import java.util.stream.Stream;
+import org.apache.ignite.internal.cli.commands.CliCommandTestBase;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class SqlHelpCommandTest extends CliCommandTestBase {
+
+    private static Stream<Arguments> sqlCommands() {
+        return Stream.of(IgniteSqlCommand.values())
+                .map(command -> Arguments.of(command.getTopic(), 
command.getSyntax()));
+    }
+
+    @Override
+    protected Class<?> getCommandClass() {
+        return SqlHelpCommand.class;
+    }
+
+    @Test
+    @DisplayName("Should throw error if provided wring SQL command")
+    void wrongSqlCommand() {
+        execute("wrongcommand");
+
+        assertAll(
+                () -> assertExitCodeIs(1),
+                this::assertOutputIsEmpty,
+                () -> assertErrOutputContains("Unknown command:")
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("sqlCommands")
+    @DisplayName("Should display a command syntax if provided a valid command")
+    void validSqlCommand(String commandToShowHelpFor, String 
expectedHelpMessage) {
+        execute(commandToShowHelpFor);
+
+        assertAll(
+                () -> assertExitCodeIs(0),
+                () -> assertOutputContains(expectedHelpMessage),
+                this::assertErrOutputIsEmpty);
+    }
+}

Reply via email to