You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ta...@apache.org on 2021/11/27 05:24:21 UTC

[iotdb] 03/10: [IOTDB-1673] CLI refactor and upgrade to JLine3 (#4458)

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

tanxinyu pushed a commit to branch master_performance
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit 89d5c48e9978d38463579d462a5be9e8f2e8b869
Author: Zhong Wang <wa...@alibaba-inc.com>
AuthorDate: Thu Nov 25 22:58:15 2021 +0800

    [IOTDB-1673] CLI refactor and upgrade to JLine3 (#4458)
    
    * [IOTDB-1673] CLI refactor
    
    1. Upgrade to Jline3.
    2. Remove the CLI implmentation for Windows since Jline3 is platform independent.
    3. Support persisted command history.
    4. Support multi-line edition.
    5. Exit by pressing CTRL+D or CTRL+C twice.
    6. Add auto pair and auto suggestion widgets.
    7. Add keyword highlighter and completer.
    
    * Resolve comment
    
    1. Update LICENSE-binary
---
 LICENSE-binary                                     |   6 +-
 .../org/apache/iotdb/db/qp/sql/IoTDBSqlLexer.g4    |   4 +-
 cli/pom.xml                                        |  11 +-
 cli/src/assembly/resources/sbin/start-cli.bat      |   2 +-
 cli/src/main/java/org/apache/iotdb/cli/Cli.java    |  40 +++--
 .../apache/iotdb/cli/IoTDBSyntaxHighlighter.java   |  76 +++++++++
 cli/src/main/java/org/apache/iotdb/cli/WinCli.java | 175 ---------------------
 .../org/apache/iotdb/cli/utils/JlineUtils.java     | 107 +++++++++++++
 .../org/apache/iotdb/tool/AbstractCsvTool.java     |   3 +-
 .../main/java/org/apache/iotdb/tool/ExportCsv.java |  19 ++-
 pom.xml                                            |   6 +-
 11 files changed, 240 insertions(+), 209 deletions(-)

diff --git a/LICENSE-binary b/LICENSE-binary
index ce5c2aa..de449d9 100644
--- a/LICENSE-binary
+++ b/LICENSE-binary
@@ -271,16 +271,12 @@ org.xerial.snappy:snappy-java:1.1.8.4
 io.airlift.airline:0.8
 net.minidev:accessors-smart:1.2
 
-BSD 2-Clause
-------------
-jline:jline:2.14.5
-
 
 BSD 3-Clause
 ------------
 org.antlr:antlr-runtime:4.8-1
 org.ow2.asm:asm:5.0.4
-org.jline:jline:3.10.0
+org.jline:jline:3.21.0
 
 
 MIT License
diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlLexer.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlLexer.g4
index fdcd67f..be310fb 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlLexer.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlLexer.g4
@@ -23,9 +23,11 @@ lexer grammar IoTDBSqlLexer;
  * 1. Whitespace
  */
 
+// Instead of discarding whitespace completely, send them to a channel invisable to the parser, so
+// that the lexer could still produce WS tokens for the CLI's highlighter.
 WS
     :
-    [ \u000B\t\r\n]+ -> skip
+    [ \u000B\t\r\n]+ -> channel(HIDDEN)
     ;
 
 
diff --git a/cli/pom.xml b/cli/pom.xml
index 9bcb861..741ff6d 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -66,10 +66,19 @@
             <version>0.9.2</version>
         </dependency>
         <dependency>
-            <groupId>jline</groupId>
+            <groupId>org.jline</groupId>
             <artifactId>jline</artifactId>
         </dependency>
         <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.iotdb</groupId>
+            <artifactId>iotdb-antlr</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>org.apache.iotdb</groupId>
             <artifactId>iotdb-server</artifactId>
             <version>${project.version}</version>
diff --git a/cli/src/assembly/resources/sbin/start-cli.bat b/cli/src/assembly/resources/sbin/start-cli.bat
index 99da3fd..3868786 100644
--- a/cli/src/assembly/resources/sbin/start-cli.bat
+++ b/cli/src/assembly/resources/sbin/start-cli.bat
@@ -28,7 +28,7 @@ pushd %~dp0..
 if NOT DEFINED IOTDB_CLI_HOME set IOTDB_CLI_HOME=%CD%
 popd
 
-if NOT DEFINED MAIN_CLASS set MAIN_CLASS=org.apache.iotdb.cli.WinCli
+if NOT DEFINED MAIN_CLASS set MAIN_CLASS=org.apache.iotdb.cli.Cli
 if NOT DEFINED JAVA_HOME goto :err
 
 @REM -----------------------------------------------------------------------------
diff --git a/cli/src/main/java/org/apache/iotdb/cli/Cli.java b/cli/src/main/java/org/apache/iotdb/cli/Cli.java
index 6e57d27..6d7675f 100644
--- a/cli/src/main/java/org/apache/iotdb/cli/Cli.java
+++ b/cli/src/main/java/org/apache/iotdb/cli/Cli.java
@@ -18,12 +18,12 @@
  */
 package org.apache.iotdb.cli;
 
+import org.apache.iotdb.cli.utils.JlineUtils;
 import org.apache.iotdb.exception.ArgsErrorException;
 import org.apache.iotdb.jdbc.Config;
 import org.apache.iotdb.jdbc.IoTDBConnection;
 import org.apache.iotdb.rpc.RpcUtils;
 
-import jline.console.ConsoleReader;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
 import org.apache.commons.cli.DefaultParser;
@@ -31,6 +31,9 @@ import org.apache.commons.cli.HelpFormatter;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
 import org.apache.thrift.TException;
+import org.jline.reader.EndOfFileException;
+import org.jline.reader.LineReader;
+import org.jline.reader.UserInterruptException;
 
 import java.io.IOException;
 import java.sql.DriverManager;
@@ -42,6 +45,7 @@ import static org.apache.iotdb.cli.utils.IoTPrinter.println;
 public class Cli extends AbstractCli {
 
   private static CommandLine commandLine;
+  private static LineReader lineReader;
 
   /**
    * IoTDB Client main function.
@@ -49,7 +53,7 @@ public class Cli extends AbstractCli {
    * @param args launch arguments
    * @throws ClassNotFoundException ClassNotFoundException
    */
-  public static void main(String[] args) throws ClassNotFoundException {
+  public static void main(String[] args) throws ClassNotFoundException, IOException {
     Class.forName(Config.JDBC_DRIVER_NAME);
     Options options = createOptions();
     HelpFormatter hf = new HelpFormatter();
@@ -72,6 +76,7 @@ public class Cli extends AbstractCli {
       return;
     }
 
+    lineReader = JlineUtils.getLineReader();
     serve();
   }
 
@@ -129,13 +134,10 @@ public class Cli extends AbstractCli {
           println(IOTDB_CLI_PREFIX + "> can't execute sql because" + e.getMessage());
         }
       }
-      try (ConsoleReader reader = new ConsoleReader()) {
-        reader.setExpandEvents(false);
-        if (password == null) {
-          password = reader.readLine("please input your password:", '\0');
-        }
-        receiveCommands(reader);
+      if (password == null) {
+        password = lineReader.readLine("please input your password:", '\0');
       }
+      receiveCommands(lineReader);
     } catch (ArgsErrorException e) {
       println(IOTDB_CLI_PREFIX + "> input params error because" + e.getMessage());
     } catch (Exception e) {
@@ -143,7 +145,7 @@ public class Cli extends AbstractCli {
     }
   }
 
-  private static void receiveCommands(ConsoleReader reader) throws TException, IOException {
+  private static void receiveCommands(LineReader reader) throws TException {
     try (IoTDBConnection connection =
         (IoTDBConnection)
             DriverManager.getConnection(
@@ -157,10 +159,22 @@ public class Cli extends AbstractCli {
       displayLogo(properties.getVersion());
       println(IOTDB_CLI_PREFIX + "> login successfully");
       while (true) {
-        s = reader.readLine(IOTDB_CLI_PREFIX + "> ", null);
-        boolean continues = processCommand(s, connection);
-        if (!continues) {
-          break;
+        try {
+          s = reader.readLine(IOTDB_CLI_PREFIX + "> ", null);
+          boolean continues = processCommand(s, connection);
+          if (!continues) {
+            break;
+          }
+        } catch (UserInterruptException e) {
+          // Exit on signal INT requires confirmation.
+          try {
+            reader.readLine("Press CTRL+C again to exit, or press ENTER to continue", '\0');
+          } catch (UserInterruptException | EndOfFileException e2) {
+            System.exit(0);
+          }
+        } catch (EndOfFileException e) {
+          // Exit on EOF (usually by pressing CTRL+D).
+          System.exit(0);
         }
       }
     } catch (SQLException e) {
diff --git a/cli/src/main/java/org/apache/iotdb/cli/IoTDBSyntaxHighlighter.java b/cli/src/main/java/org/apache/iotdb/cli/IoTDBSyntaxHighlighter.java
new file mode 100644
index 0000000..82e9866
--- /dev/null
+++ b/cli/src/main/java/org/apache/iotdb/cli/IoTDBSyntaxHighlighter.java
@@ -0,0 +1,76 @@
+/*
+ * 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.iotdb.cli;
+
+import org.apache.iotdb.cli.utils.JlineUtils;
+import org.apache.iotdb.db.qp.sql.IoTDBSqlLexer;
+
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.Token;
+import org.jline.reader.Highlighter;
+import org.jline.reader.LineReader;
+import org.jline.utils.AttributedString;
+import org.jline.utils.AttributedStringBuilder;
+import org.jline.utils.AttributedStyle;
+
+import java.util.regex.Pattern;
+
+import static org.jline.utils.AttributedStyle.DEFAULT;
+import static org.jline.utils.AttributedStyle.GREEN;
+
+public class IoTDBSyntaxHighlighter implements Highlighter {
+
+  private static final AttributedStyle KEYWORD_STYLE = DEFAULT.foreground(GREEN);
+
+  @Override
+  public AttributedString highlight(LineReader reader, String buffer) {
+    CharStream stream = CharStreams.fromString(buffer);
+    IoTDBSqlLexer tokenSource = new IoTDBSqlLexer(stream);
+    tokenSource.removeErrorListeners();
+    AttributedStringBuilder builder = new AttributedStringBuilder();
+    while (true) {
+      Token token = tokenSource.nextToken();
+      int type = token.getType();
+      if (type == Token.EOF) {
+        break;
+      }
+      String text = token.getText();
+
+      if (isKeyword(text)) {
+        builder.styled(KEYWORD_STYLE, text);
+      } else {
+        builder.append(text);
+      }
+    }
+
+    return builder.toAttributedString();
+  }
+
+  @Override
+  public void setErrorPattern(Pattern errorPattern) {}
+
+  @Override
+  public void setErrorIndex(int errorIndex) {}
+
+  private boolean isKeyword(String token) {
+    return JlineUtils.SQL_KEYWORDS.contains(token.toUpperCase());
+  }
+}
diff --git a/cli/src/main/java/org/apache/iotdb/cli/WinCli.java b/cli/src/main/java/org/apache/iotdb/cli/WinCli.java
deleted file mode 100644
index 7d9e938..0000000
--- a/cli/src/main/java/org/apache/iotdb/cli/WinCli.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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.iotdb.cli;
-
-import org.apache.iotdb.exception.ArgsErrorException;
-import org.apache.iotdb.jdbc.Config;
-import org.apache.iotdb.jdbc.IoTDBConnection;
-import org.apache.iotdb.rpc.RpcUtils;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
-import org.apache.thrift.TException;
-
-import java.io.Console;
-import java.sql.DriverManager;
-import java.sql.SQLException;
-import java.util.Scanner;
-
-import static org.apache.iotdb.cli.utils.IoTPrinter.print;
-import static org.apache.iotdb.cli.utils.IoTPrinter.println;
-
-/** args[]: -h 127.0.0.1 -p 6667 -u root -pw root */
-public class WinCli extends AbstractCli {
-
-  private static CommandLine commandLine;
-
-  /**
-   * main function.
-   *
-   * @param args -console args
-   */
-  public static void main(String[] args) throws ClassNotFoundException {
-    Class.forName(Config.JDBC_DRIVER_NAME);
-    Options options = createOptions();
-    HelpFormatter hf = new HelpFormatter();
-    hf.setWidth(MAX_HELP_CONSOLE_WIDTH);
-    commandLine = null;
-
-    if (args == null || args.length == 0) {
-      println("Require more params input, please check the following hint.");
-      hf.printHelp(IOTDB_CLI_PREFIX, options, true);
-      return;
-    }
-
-    init();
-    String[] newArgs = removePasswordArgs(args);
-    String[] newArgs2 = processExecuteArgs(newArgs);
-    boolean continues = parseCommandLine(options, newArgs2, hf);
-    if (!continues) {
-      return;
-    }
-
-    serve();
-  }
-
-  private static String readPassword() {
-    Console c = System.console();
-    if (c == null) { // IN ECLIENTPSE IDE
-      print(IOTDB_CLI_PREFIX + "> please input password: ");
-      Scanner scanner = new Scanner(System.in);
-      return scanner.nextLine();
-    } else { // Outside Eclipse IDE
-      return new String(c.readPassword(IOTDB_CLI_PREFIX + "> please input password: "));
-    }
-  }
-
-  private static boolean parseCommandLine(Options options, String[] newArgs, HelpFormatter hf) {
-    try {
-      CommandLineParser parser = new DefaultParser();
-      commandLine = parser.parse(options, newArgs);
-      if (commandLine.hasOption(HELP_ARGS)) {
-        hf.printHelp(IOTDB_CLI_PREFIX, options, true);
-        return false;
-      }
-      if (commandLine.hasOption(RPC_COMPRESS_ARGS)) {
-        Config.rpcThriftCompressionEnable = true;
-      }
-      if (commandLine.hasOption(ISO8601_ARGS)) {
-        timeFormat = RpcUtils.setTimeFormat("long");
-      }
-      if (commandLine.hasOption(MAX_PRINT_ROW_COUNT_ARGS)) {
-        setMaxDisplayNumber(commandLine.getOptionValue(MAX_PRINT_ROW_COUNT_ARGS));
-      }
-    } catch (ParseException e) {
-      println("Require more params input, please check the following hint.");
-      hf.printHelp(IOTDB_CLI_PREFIX, options, true);
-      return false;
-    } catch (NumberFormatException e) {
-      println(
-          IOTDB_CLI_PREFIX
-              + "> error format of max print row count, it should be an integer number");
-      return false;
-    }
-    return true;
-  }
-
-  private static void serve() {
-    try (Scanner scanner = new Scanner(System.in)) {
-      host = checkRequiredArg(HOST_ARGS, HOST_NAME, commandLine, false, host);
-      port = checkRequiredArg(PORT_ARGS, PORT_NAME, commandLine, false, port);
-      username = checkRequiredArg(USERNAME_ARGS, USERNAME_NAME, commandLine, true, null);
-      password = commandLine.getOptionValue(PASSWORD_ARGS);
-      if (password == null) {
-        password = readPassword();
-      }
-      if (hasExecuteSQL) {
-        try (IoTDBConnection connection =
-            (IoTDBConnection)
-                DriverManager.getConnection(
-                    Config.IOTDB_URL_PREFIX + host + ":" + port + "/", username, password)) {
-          properties = connection.getServerProperties();
-          timestampPrecision = properties.getTimestampPrecision();
-          AGGREGRATE_TIME_LIST.addAll(properties.getSupportedTimeAggregationOperations());
-          processCommand(execute, connection);
-          return;
-        } catch (SQLException e) {
-          println(IOTDB_CLI_PREFIX + "> can't execute sql because" + e.getMessage());
-        }
-      }
-
-      receiveCommands(scanner);
-    } catch (ArgsErrorException e) {
-      println(IOTDB_CLI_PREFIX + "> input params error because" + e.getMessage());
-    } catch (Exception e) {
-      println(IOTDB_CLI_PREFIX + "> exit cli with error " + e.getMessage());
-    }
-  }
-
-  private static void receiveCommands(Scanner scanner) throws TException {
-    try (IoTDBConnection connection =
-        (IoTDBConnection)
-            DriverManager.getConnection(
-                Config.IOTDB_URL_PREFIX + host + ":" + port + "/", username, password)) {
-      properties = connection.getServerProperties();
-      AGGREGRATE_TIME_LIST.addAll(properties.getSupportedTimeAggregationOperations());
-      timestampPrecision = properties.getTimestampPrecision();
-
-      echoStarting();
-      displayLogo(properties.getVersion());
-      println(IOTDB_CLI_PREFIX + "> login successfully");
-      while (true) {
-        print(IOTDB_CLI_PREFIX + "> ");
-        String s = scanner.nextLine();
-        boolean continues = processCommand(s, connection);
-        if (!continues) {
-          break;
-        }
-      }
-    } catch (SQLException e) {
-      println(
-          String.format(
-              "%s> %s Host is %s, port is %s.", IOTDB_CLI_PREFIX, e.getMessage(), host, port));
-    }
-  }
-}
diff --git a/cli/src/main/java/org/apache/iotdb/cli/utils/JlineUtils.java b/cli/src/main/java/org/apache/iotdb/cli/utils/JlineUtils.java
new file mode 100644
index 0000000..e2f5c7d
--- /dev/null
+++ b/cli/src/main/java/org/apache/iotdb/cli/utils/JlineUtils.java
@@ -0,0 +1,107 @@
+/*
+ * 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.iotdb.cli.utils;
+
+import org.apache.iotdb.cli.IoTDBSyntaxHighlighter;
+import org.apache.iotdb.db.qp.sql.IoTDBSqlLexer;
+
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReader.Option;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.reader.impl.DefaultParser.Bracket;
+import org.jline.reader.impl.completer.StringsCompleter;
+import org.jline.terminal.Size;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.Terminal.Signal;
+import org.jline.terminal.TerminalBuilder;
+import org.jline.utils.OSUtils;
+import org.jline.widget.AutopairWidgets;
+import org.jline.widget.AutosuggestionWidgets;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class JlineUtils {
+
+  public static final Pattern SQL_KEYWORD_PATTERN = Pattern.compile("([A-Z_]+)");
+  public static final Set<String> SQL_KEYWORDS =
+      IntStream.range(0, IoTDBSqlLexer.VOCABULARY.getMaxTokenType())
+          .mapToObj(IoTDBSqlLexer.VOCABULARY::getDisplayName)
+          .filter(Objects::nonNull)
+          .filter(w -> SQL_KEYWORD_PATTERN.matcher(w).matches())
+          .collect(Collectors.toSet());
+
+  public static LineReader getLineReader() throws IOException {
+    Terminal terminal = TerminalBuilder.builder().build();
+    if (terminal.getWidth() == 0 || terminal.getHeight() == 0) {
+      // Hard coded terminal size when redirecting.
+      terminal.setSize(new Size(120, 40));
+    }
+    Thread executeThread = Thread.currentThread();
+    // Register signal handler. Instead of shutting down the process, interrupt the current thread
+    // when signal INT is received (usually by pressing CTRL+C).
+    terminal.handle(Signal.INT, signal -> executeThread.interrupt());
+
+    LineReaderBuilder builder = LineReaderBuilder.builder();
+    builder.terminal(terminal);
+
+    // Handle the command history. By default, the number of commands will not exceed 500 and the
+    // size of the history fill will be less than 10 KB. See:
+    // org.jline.reader.impl.history#DefaultHistory
+    String historyFile = ".iotdb.history";
+    String historyFilePath = System.getProperty("user.home") + File.separator + historyFile;
+    builder.variable(LineReader.HISTORY_FILE, new File(historyFilePath));
+
+    builder.highlighter(new IoTDBSyntaxHighlighter());
+
+    builder.completer(new StringsCompleter(SQL_KEYWORDS));
+
+    builder.option(Option.CASE_INSENSITIVE_SEARCH, true);
+    builder.option(Option.CASE_INSENSITIVE, true);
+    // See: https://www.gnu.org/software/bash/manual/html_node/Event-Designators.html
+    builder.option(Option.DISABLE_EVENT_EXPANSION, true);
+
+    org.jline.reader.impl.DefaultParser parser = new org.jline.reader.impl.DefaultParser();
+    // Make multi-line edition be triggered by unclosed brackets and unclosed quotes.
+    parser.setEofOnUnclosedBracket(Bracket.CURLY, Bracket.SQUARE, Bracket.ROUND);
+    parser.setEofOnUnclosedQuote(true);
+    builder.parser(parser);
+    LineReader lineReader = builder.build();
+    if (OSUtils.IS_WINDOWS) {
+      // If enabled cursor remains in begin parenthesis (gitbash).
+      lineReader.setVariable(LineReader.BLINK_MATCHING_PAREN, 0);
+    }
+
+    // Create auto-pair widgets
+    AutopairWidgets autopairWidgets = new AutopairWidgets(lineReader);
+    // Enable auto-pair
+    autopairWidgets.enable();
+    // Create autosuggestion widgets
+    AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(lineReader);
+    // Enable autosuggestions
+    autosuggestionWidgets.enable();
+    return lineReader;
+  }
+}
diff --git a/cli/src/main/java/org/apache/iotdb/tool/AbstractCsvTool.java b/cli/src/main/java/org/apache/iotdb/tool/AbstractCsvTool.java
index f03275d..3294406 100644
--- a/cli/src/main/java/org/apache/iotdb/tool/AbstractCsvTool.java
+++ b/cli/src/main/java/org/apache/iotdb/tool/AbstractCsvTool.java
@@ -23,7 +23,6 @@ import org.apache.iotdb.rpc.IoTDBConnectionException;
 import org.apache.iotdb.rpc.StatementExecutionException;
 import org.apache.iotdb.session.Session;
 
-import jline.internal.Nullable;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
@@ -210,7 +209,7 @@ public abstract class AbstractCsvTool {
    * @param filePath the directory to save the file
    */
   public static Boolean writeCsvFile(
-      @Nullable List<String> headerNames, List<List<Object>> records, String filePath) {
+      List<String> headerNames, List<List<Object>> records, String filePath) {
     try {
       CSVPrinter printer =
           CSVFormat.DEFAULT
diff --git a/cli/src/main/java/org/apache/iotdb/tool/ExportCsv.java b/cli/src/main/java/org/apache/iotdb/tool/ExportCsv.java
index 3450121..6d77a36 100644
--- a/cli/src/main/java/org/apache/iotdb/tool/ExportCsv.java
+++ b/cli/src/main/java/org/apache/iotdb/tool/ExportCsv.java
@@ -19,6 +19,7 @@
 
 package org.apache.iotdb.tool;
 
+import org.apache.iotdb.cli.utils.JlineUtils;
 import org.apache.iotdb.exception.ArgsErrorException;
 import org.apache.iotdb.rpc.IoTDBConnectionException;
 import org.apache.iotdb.rpc.RpcUtils;
@@ -28,7 +29,6 @@ import org.apache.iotdb.session.SessionDataSet;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.read.common.RowRecord;
 
-import jline.console.ConsoleReader;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
 import org.apache.commons.cli.DefaultParser;
@@ -39,8 +39,13 @@ import org.apache.commons.cli.ParseException;
 import org.apache.commons.csv.CSVFormat;
 import org.apache.commons.csv.CSVPrinter;
 import org.apache.commons.csv.QuoteMode;
+import org.jline.reader.LineReader;
 
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
 import java.time.Instant;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
@@ -83,7 +88,7 @@ public class ExportCsv extends AbstractCsvTool {
   private static final int EXPORT_PER_LINE_COUNT = 10000;
 
   /** main function of export csv tool. */
-  public static void main(String[] args) throws IOException {
+  public static void main(String[] args) {
     Options options = createOptions();
     HelpFormatter hf = new HelpFormatter();
     CommandLine commandLine;
@@ -124,15 +129,13 @@ public class ExportCsv extends AbstractCsvTool {
         String sql;
 
         if (sqlFile == null) {
-          ConsoleReader reader = new ConsoleReader();
-          reader.setExpandEvents(false);
-          sql = reader.readLine(TSFILEDB_CLI_PREFIX + "> please input query: ");
+          LineReader lineReader = JlineUtils.getLineReader();
+          sql = lineReader.readLine(TSFILEDB_CLI_PREFIX + "> please input query: ");
           System.out.println(sql);
           String[] values = sql.trim().split(";");
           for (int i = 0; i < values.length; i++) {
             dumpResult(values[i], i);
           }
-          reader.close();
         } else {
           dumpFromSqlFile(sqlFile);
         }
@@ -335,7 +338,7 @@ public class ExportCsv extends AbstractCsvTool {
         }
       }
     } else {
-      names.forEach(name -> headers.add(name));
+      headers.addAll(names);
     }
     printer.printRecord(headers);
 
diff --git a/pom.xml b/pom.xml
index 6286eef..37f2f27 100644
--- a/pom.xml
+++ b/pom.xml
@@ -137,7 +137,7 @@
         <common.pool2.version>2.11.1</common.pool2.version>
         <org.slf4j.version>1.7.32</org.slf4j.version>
         <guava.version>24.1.1</guava.version>
-        <jline.version>2.14.6</jline.version>
+        <jline.version>3.21.0</jline.version>
         <jetty.version>9.4.35.v20201120</jetty.version>
         <metrics.version>4.2.4</metrics.version>
         <javax.xml.bind.version>2.4.0-b180830.0359</javax.xml.bind.version>
@@ -269,7 +269,7 @@
                 <version>${javax.xml.bind.version}</version>
             </dependency>
             <dependency>
-                <groupId>jline</groupId>
+                <groupId>org.jline</groupId>
                 <artifactId>jline</artifactId>
                 <version>${jline.version}</version>
             </dependency>
@@ -508,7 +508,7 @@
                 <artifactId>gson</artifactId>
                 <version>${gson.version}</version>
             </dependency>
-            <!-- for test container -->
+            <!-- for cli and test container -->
             <dependency>
                 <groupId>net.java.dev.jna</groupId>
                 <artifactId>jna</artifactId>