This is an automated email from the ASF dual-hosted git repository. abstractdog pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/hive.git
The following commit(s) were added to refs/heads/master by this push: new ac1f60ab4d2 HIVE-25495: Upgrade to JLine3 (#5838) ac1f60ab4d2 is described below commit ac1f60ab4d21f9d19b4759b3f248a5dea7c7d26d Author: Bodor Laszlo <bodorlaszlo0...@gmail.com> AuthorDate: Mon Jul 28 10:24:10 2025 +0200 HIVE-25495: Upgrade to JLine3 (#5838) --- beeline/pom.xml | 2 +- .../hive/beeline/AbstractCommandHandler.java | 14 +- .../src/java/org/apache/hive/beeline/BeeLine.java | 286 ++++++++++++++------- .../hive/beeline/BeeLineCommandCompleter.java | 18 +- .../org/apache/hive/beeline/BeeLineCompleter.java | 23 +- .../HiveCli.java => BeeLineDummyTerminal.java} | 35 +-- .../java/org/apache/hive/beeline/BeeLineOpts.java | 41 +-- .../org/apache/hive/beeline/BooleanCompleter.java | 11 +- .../org/apache/hive/beeline/CommandHandler.java | 6 +- .../src/java/org/apache/hive/beeline/Commands.java | 37 ++- .../apache/hive/beeline/DatabaseConnection.java | 27 +- ...eter.java => NoCurrentConnectionException.java} | 15 +- .../hive/beeline/ReflectiveCommandHandler.java | 4 +- .../java/org/apache/hive/beeline/SQLCompleter.java | 4 +- ...eNameCompletor.java => TableNameCompleter.java} | 23 +- .../java/org/apache/hive/beeline/cli/HiveCli.java | 6 +- .../hive/beeline/schematool/HiveSchemaTool.java | 3 +- .../apache/hive/beeline/TestBeeLineHistory.java | 16 +- .../test/org/apache/hive/beeline/TestCommands.java | 8 +- .../apache/hive/beeline/cli/HiveCliForTest.java} | 18 +- .../org/apache/hive/beeline/cli/TestHiveCli.java | 11 +- cli/pom.xml | 2 +- .../java/org/apache/hadoop/hive/cli/CliDriver.java | 216 ++++++++-------- .../hadoop/hive/cli/TestCliDriverMethods.java | 174 +++++++------ common/pom.xml | 3 +- .../hive/common/util/MatchingStringsCompleter.java | 70 +++++ hcatalog/hcatalog-pig-adapter/pom.xml | 6 +- .../apache/hive/beeline/TestBeeLineWithArgs.java | 67 +++-- .../hive/beeline/TestBeelinePasswordOption.java | 36 +-- .../apache/hive/beeline/TestHplSqlViaBeeLine.java | 8 +- .../BeelineWithHS2ConnectionFileTestBase.java | 14 +- .../miniHS2/TestHs2ConnectionMetricsBinary.java | 3 +- .../InformationSchemaWithPrivilegeTestBase.java | 3 +- .../llap/cli/service/LlapServiceCommandLine.java | 5 +- .../cli/status/LlapStatusServiceCommandLine.java | 6 +- pom.xml | 10 +- standalone-metastore/metastore-server/pom.xml | 2 +- .../tools/metatool/HiveMetaToolCommandLine.java | 5 +- standalone-metastore/pom.xml | 11 +- 39 files changed, 713 insertions(+), 536 deletions(-) diff --git a/beeline/pom.xml b/beeline/pom.xml index c482416de51..a806eb29d5c 100644 --- a/beeline/pom.xml +++ b/beeline/pom.xml @@ -80,7 +80,7 @@ <artifactId>jackson-core</artifactId> </dependency> <dependency> - <groupId>jline</groupId> + <groupId>org.jline</groupId> <artifactId>jline</artifactId> </dependency> <dependency> diff --git a/beeline/src/java/org/apache/hive/beeline/AbstractCommandHandler.java b/beeline/src/java/org/apache/hive/beeline/AbstractCommandHandler.java index 7fc3f958f5d..cd250ca2906 100644 --- a/beeline/src/java/org/apache/hive/beeline/AbstractCommandHandler.java +++ b/beeline/src/java/org/apache/hive/beeline/AbstractCommandHandler.java @@ -26,8 +26,8 @@ import java.util.LinkedList; import java.util.List; -import jline.console.completer.Completer; -import jline.console.completer.NullCompleter; +import org.jline.reader.Completer; +import org.jline.reader.impl.completer.NullCompleter; /** * An abstract implementation of CommandHandler. @@ -43,15 +43,15 @@ public abstract class AbstractCommandHandler implements CommandHandler { protected transient Throwable lastException; public AbstractCommandHandler(BeeLine beeLine, String[] names, String helpText, - Completer[] completors) { + Completer[] completers) { this.beeLine = beeLine; name = names[0]; this.names = names; this.helpText = helpText; - if (completors == null || completors.length == 0) { + if (completers == null || completers.length == 0) { parameterCompleters = new Completer[] { new NullCompleter() }; } else { - List<Completer> c = new LinkedList<Completer>(Arrays.asList(completors)); + List<Completer> c = new LinkedList<Completer>(Arrays.asList(completers)); c.add(new NullCompleter()); parameterCompleters = c.toArray(new Completer[0]); } @@ -94,10 +94,6 @@ public String matches(String line) { return null; } - public void setParameterCompleters(Completer[] parameterCompleters) { - this.parameterCompleters = parameterCompleters; - } - @Override public Completer[] getParameterCompleters() { return parameterCompleters; diff --git a/beeline/src/java/org/apache/hive/beeline/BeeLine.java b/beeline/src/java/org/apache/hive/beeline/BeeLine.java index 515d33f8434..3cbbc826d8d 100644 --- a/beeline/src/java/org/apache/hive/beeline/BeeLine.java +++ b/beeline/src/java/org/apache/hive/beeline/BeeLine.java @@ -87,7 +87,6 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.hive.conf.Constants; import org.apache.hadoop.hive.conf.HiveConf; @@ -101,6 +100,7 @@ import org.apache.hive.beeline.hs2connection.HS2ConnectionFileUtils; import org.apache.hive.beeline.hs2connection.HiveSiteHS2ConnectionFileParser; import org.apache.hive.beeline.hs2connection.UserHS2ConnectionFileParser; +import org.apache.hive.common.util.MatchingStringsCompleter; import org.apache.hive.common.util.ShutdownHookManager; import org.apache.hive.common.util.HiveStringUtils; import org.apache.hive.jdbc.HiveConnection; @@ -111,11 +111,19 @@ import com.google.common.annotations.VisibleForTesting; -import jline.console.ConsoleReader; -import jline.console.completer.Completer; -import jline.console.completer.FileNameCompleter; -import jline.console.completer.StringsCompleter; -import jline.console.history.FileHistory; +import org.jline.reader.Completer; +import org.jline.reader.EndOfFileException; +import org.jline.reader.History; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.impl.DefaultParser; +import org.jline.reader.impl.LineReaderImpl; +import org.jline.reader.impl.history.DefaultHistory; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; + +import static org.jline.builtins.Completers.FileNameCompleter; + /** * A console SQL shell with command completion. @@ -152,14 +160,14 @@ public class BeeLine implements Closeable { private OutputFile recordOutputFile = null; private PrintStream outputStream = new PrintStream(System.out, true); private PrintStream errorStream = new PrintStream(System.err, true); - private InputStream inputStream = System.in; - private ConsoleReader consoleReader; + private LineReader lineReader; + private final List<Terminal> terminalsToClose = new ArrayList<>(); private List<String> batch = null; private final Reflector reflector = new Reflector(this); private String dbName = null; private String currentDatabase = null; - private FileHistory history; + private History history; // Indicates if this instance of beeline is running in compatibility mode, or beeline mode private boolean isBeeLine = true; @@ -205,19 +213,19 @@ public class BeeLine implements Closeable { new ReflectiveCommandHandler(this, new String[] {"quit", "done", "exit"}, null), new ReflectiveCommandHandler(this, new String[] {"connect", "open"}, - new Completer[] {new StringsCompleter(getConnectionURLExamples())}), + new Completer[] {new MatchingStringsCompleter(getConnectionURLExamples())}), new ReflectiveCommandHandler(this, new String[] {"describe"}, - new Completer[] {new TableNameCompletor(this)}), + new Completer[] {new TableNameCompleter(this)}), new ReflectiveCommandHandler(this, new String[] {"indexes"}, - new Completer[] {new TableNameCompletor(this)}), + new Completer[] {new TableNameCompleter(this)}), new ReflectiveCommandHandler(this, new String[] {"primarykeys"}, - new Completer[] {new TableNameCompletor(this)}), + new Completer[] {new TableNameCompleter(this)}), new ReflectiveCommandHandler(this, new String[] {"exportedkeys"}, - new Completer[] {new TableNameCompletor(this)}), + new Completer[] {new TableNameCompleter(this)}), new ReflectiveCommandHandler(this, new String[] {"manual"}, null), new ReflectiveCommandHandler(this, new String[] {"importedkeys"}, - new Completer[] {new TableNameCompletor(this)}), + new Completer[] {new TableNameCompleter(this)}), new ReflectiveCommandHandler(this, new String[] {"procedures"}, null), new ReflectiveCommandHandler(this, new String[] {"tables"}, @@ -225,16 +233,16 @@ public class BeeLine implements Closeable { new ReflectiveCommandHandler(this, new String[] {"typeinfo"}, null), new ReflectiveCommandHandler(this, new String[] {"columns"}, - new Completer[] {new TableNameCompletor(this)}), + new Completer[] {new TableNameCompleter(this)}), new ReflectiveCommandHandler(this, new String[] {"reconnect"}, null), new ReflectiveCommandHandler(this, new String[] {"dropall"}, - new Completer[] {new TableNameCompletor(this)}), + new Completer[] {new TableNameCompleter(this)}), new ReflectiveCommandHandler(this, new String[] {"history"}, null), new ReflectiveCommandHandler(this, new String[] {"metadata"}, new Completer[] { - new StringsCompleter(getMetadataMethodNames())}), + new MatchingStringsCompleter(getMetadataMethodNames())}), new ReflectiveCommandHandler(this, new String[] {"nativesql"}, null), new ReflectiveCommandHandler(this, new String[] {"dbinfo"}, @@ -264,9 +272,9 @@ public class BeeLine implements Closeable { new ReflectiveCommandHandler(this, new String[] {"closeall"}, null), new ReflectiveCommandHandler(this, new String[] {"isolation"}, - new Completer[] {new StringsCompleter(getIsolationLevels())}), + new Completer[] {new MatchingStringsCompleter(getIsolationLevels())}), new ReflectiveCommandHandler(this, new String[] {"outputformat"}, - new Completer[] {new StringsCompleter( + new Completer[] {new MatchingStringsCompleter( formats.keySet().toArray(new String[0]))}), new ReflectiveCommandHandler(this, new String[] {"autocommit"}, null), @@ -310,9 +318,9 @@ public class BeeLine implements Closeable { static { try { - Class.forName("jline.console.ConsoleReader"); + Class.forName("org.jline.reader.LineReader"); } catch (Throwable t) { - throw new ExceptionInInitializerError("jline-missing"); + throw new ExceptionInInitializerError("jline3-missing"); } } @@ -401,7 +409,7 @@ public class BeeLine implements Closeable { .withLongOpt("help") .withDescription("Display this message") .create('h')); - + // -getUrlsFromBeelineSite options.addOption(OptionBuilder .withLongOpt("getUrlsFromBeelineSite") @@ -434,7 +442,6 @@ public class BeeLine implements Closeable { .create()); } - static Manifest getManifest() throws IOException { URL base = BeeLine.class.getResource("/META-INF/MANIFEST.MF"); URLConnection c = base.openConnection(); @@ -444,7 +451,6 @@ static Manifest getManifest() throws IOException { return null; } - String getManifestAttribute(String name) { try { Manifest m = getManifest(); @@ -552,6 +558,11 @@ public static void main(String[] args) throws IOException { public static void mainWithInputRedirection(String[] args, InputStream inputStream) throws IOException { BeeLine beeLine = new BeeLine(); + mainWithInputRedirection(args, inputStream, beeLine); + } + + public static void mainWithInputRedirection(String[] args, InputStream inputStream, BeeLine beeLine) + throws IOException { try { int status = beeLine.begin(args, inputStream); @@ -570,19 +581,15 @@ public BeeLine() { public BeeLine(boolean isBeeLine) { this.isBeeLine = isBeeLine; this.signalHandler = new SunSignalHandler(this); - this.shutdownHook = new Runnable() { - @Override - public void run() { - try { - if (history != null) { - history.setMaxSize(getOpts().getMaxHistoryRows()); - history.flush(); - } - } catch (IOException e) { - error(e); - } finally { - close(); + this.shutdownHook = () -> { + try { + if (history != null) { + history.save(); } + } catch (IOException e) { + error(e); + } finally { + close(); } }; } @@ -603,10 +610,10 @@ Connection getConnection() throws SQLException { DatabaseMetaData getDatabaseMetaData() { if (getDatabaseConnections().current() == null) { - throw new IllegalArgumentException(loc("no-current-connection")); + throw new NoCurrentConnectionException(loc("no-current-connection")); } if (getDatabaseConnections().current().getDatabaseMetaData() == null) { - throw new IllegalArgumentException(loc("no-current-connection")); + throw new NoCurrentConnectionException(loc("no-current-connection")); } return getDatabaseConnections().current().getDatabaseMetaData(); } @@ -864,7 +871,7 @@ private boolean connectUsingArgs(BeelineParser beelineParser, CommandLine cl) { getOpts().setHelpAsked(true); return true; } - + if (cl.hasOption("getUrlsFromBeelineSite")) { printBeelineSiteUrls(); getOpts().setBeelineSiteUrlsAsked(true); @@ -937,11 +944,7 @@ private boolean connectUsingArgs(BeelineParser beelineParser, CommandLine cl) { // load property file String propertyFile = cl.getOptionValue("property-file"); if (propertyFile != null) { - try { - this.consoleReader = new ConsoleReader(); - } catch (IOException e) { - handleException(e); - } + this.lineReader = LineReaderBuilder.builder().build(); if (!dispatch("!properties " + propertyFile)) { exit = true; return false; @@ -981,7 +984,7 @@ private void printBeelineSiteUrls() { } } } - + private boolean isZkBasedUrl(String urlFromBeelineSite) { String zkJdbcUriParam = ("serviceDiscoveryMode=zooKeeper").toLowerCase(); if (urlFromBeelineSite.toLowerCase().contains(zkJdbcUriParam)) { @@ -1117,9 +1120,9 @@ public int begin(String[] args, InputStream inputStream, boolean keepHistory) th //add shutdown hook to cleanup the beeline for smooth exit addBeelineShutdownHook(); - //this method also initializes the consoleReader which is + //this method also initializes the lineReader which is //needed by initArgs for certain execution paths - ConsoleReader reader = initializeConsoleReader(inputStream); + LineReader interactiveLineReader = initializeLineReader(inputStream); if (isBeeLine) { int code = initArgs(args); if (code != 0) { @@ -1147,7 +1150,9 @@ public int begin(String[] args, InputStream inputStream, boolean keepHistory) th } catch (Exception e) { // ignore } - return execute(reader, false); + // at this point, begin phase is finished and beeline is ready for interactive commands + this.lineReader = interactiveLineReader; + return execute(interactiveLineReader, false); } /* @@ -1351,7 +1356,7 @@ private int executeFile(String fileName) { } fileStream = fs.open(path); } - return execute(initializeConsoleReader(fileStream), !getOpts().getForce()); + return executeFile(fileStream); } catch (Throwable t) { handleException(t); return ERRNO_OTHER; @@ -1360,17 +1365,28 @@ private int executeFile(String fileName) { } } - private int execute(ConsoleReader reader, boolean exitOnError) { + private int executeFile(InputStream fileStream) throws IOException { + // This assignment is necessary because other classes (like Commands) call BeeLine.readLine(prompt, mask) + // without needing to be aware of which type of reader is currently in use + this.lineReader = getFileLineReader(fileStream); + int retCode = execute(lineReader, !getOpts().getForce()); + // nullify for clarity's sake, as this variable will later be assigned to the interactive line reader + lineReader = null; + return retCode; + } + + @VisibleForTesting + int execute(LineReader reader, boolean exitOnError) { int lastExecutionResult = ERRNO_OK; Character mask = (System.getProperty("jline.terminal", "").equals("jline.UnsupportedTerminal")) ? null - : ConsoleReader.NULL_MASK; + : LineReaderImpl.NULL_MASK; while (!exit) { try { // Execute one instruction; terminate on executing a script if there is an error // in silent mode, prevent the query and prompt being echoed back to terminal - String line = (getOpts().isSilent() && getOpts().getScriptFile() != null) ? reader - .readLine(null, mask) : reader.readLine(getPrompt()); + String line = (getOpts().isSilent() && getOpts().getScriptFile() != null) ? readLine(reader, null, mask) : + readLine(reader, getPrompt(), null); // trim line if (line != null) { @@ -1385,7 +1401,6 @@ private int execute(ConsoleReader reader, boolean exitOnError) { } else if (line != null) { lastExecutionResult = ERRNO_OK; } - } catch (Throwable t) { handleException(t); return ERRNO_OTHER; @@ -1394,17 +1409,44 @@ private int execute(ConsoleReader reader, boolean exitOnError) { return lastExecutionResult; } + public String readLine(String prompt, Character mask) { + return readLine(getLineReader(), prompt, mask); + } + + /** + * Reads a line with the given prompt and optional mask character. + * Starting with JLine3, an EndOfFileException is intentionally thrown upon reaching the end of the input stream. + * In interactive usage, this method returns the partial line entered before the exception to preserve existing + * behavior. + */ + private String readLine(LineReader reader, String prompt, Character mask) { + try { + return reader.readLine(prompt, mask); + } catch (EndOfFileException eof) { + return eof.getPartialLine(); + } + } + @Override public void close() { commands.closeall(null); + terminalsToClose.forEach(t -> { + try { + t.close(); + } catch (IOException e) { + info(String.format("Exception while closing terminal (name: %s, class: %s): %s", t.getName(), + t.getClass(), e.getMessage())); + } + }); } - private void setupHistory() throws IOException { + @VisibleForTesting + void setupHistory() { if (this.history != null) { return; } - this.history = new FileHistory(new File(getOpts().getHistoryFile())); + this.history = new DefaultHistory(); } private void addBeelineShutdownHook() throws IOException { @@ -1412,40 +1454,107 @@ private void addBeelineShutdownHook() throws IOException { ShutdownHookManager.addShutdownHook(getShutdownHook()); } - public ConsoleReader initializeConsoleReader(InputStream inputStream) throws IOException { - if (inputStream != null) { - // ### NOTE: fix for sf.net bug 879425. - // Working around an issue in jline-2.1.2, see https://github.com/jline/jline/issues/10 - // by appending a newline to the end of inputstream - InputStream inputStreamAppendedNewline = new SequenceInputStream(inputStream, - new ByteArrayInputStream((new String("\n")).getBytes())); - consoleReader = new ConsoleReader(inputStreamAppendedNewline, getErrorStream()); - consoleReader.setCopyPasteDetection(true); // jline will detect if <tab> is regular character - } else { - consoleReader = new ConsoleReader(getInputStream(), getErrorStream()); - } + public LineReader getFileLineReader(InputStream inputStream) throws IOException { + final LineReaderBuilder builder = LineReaderBuilder.builder(); + defaultParser(builder); + + builder.terminal(buildTerminal(prepareInputStream(inputStream))); - //disable the expandEvents for the purpose of backward compatibility - consoleReader.setExpandEvents(false); + return builder.build(); + } + + public LineReader initializeLineReader(InputStream inputStream) throws IOException { + final LineReaderBuilder builder = LineReaderBuilder.builder(); + defaultParser(builder); + + Terminal lineReaderTerminal = buildTerminal(inputStream); + builder.terminal(lineReaderTerminal); try { // now set the output for the history if (this.history != null) { - consoleReader.setHistory(this.history); - } else { - consoleReader.setHistoryEnabled(false); + builder.history(this.history); + builder.variable(LineReader.HISTORY_FILE, new File(getOpts().getHistoryFile())); + builder.variable(LineReader.HISTORY_FILE_SIZE, getOpts().getMaxHistoryRows()); + // in-memory keep more data, but at least 500 entries + builder.variable(LineReader.HISTORY_SIZE, Math.max(500, 3 * getOpts().getMaxHistoryRows())); } } catch (Exception e) { handleException(e); } - if (inputStream instanceof FileInputStream || inputStream instanceof FSDataInputStream) { - // from script.. no need to load history and no need of completer, either - return consoleReader; + builder.completer(new BeeLineCompleter(this)); + lineReader = builder.build(); + lineReader.unsetOpt(LineReader.Option.HISTORY_TIMESTAMPED); + // need to disable expansion, otherwise commands (starting with "!") will activate history items + lineReader.setOpt(LineReader.Option.DISABLE_EVENT_EXPANSION); + + if (this.history != null) { + this.history.attach(lineReader); } + return lineReader; + } + + private void defaultParser(LineReaderBuilder builder) { + DefaultParser parser = new DefaultParser() { + private String extraNameCharacters; + + // delimiters for SQL statements are any + // non-letter-or-number characters, except + // underscore and characters that are specified + // by the database to be valid name identifiers. + @Override + public boolean isDelimiterChar(CharSequence buffer, int pos) { + char c = buffer.charAt(pos); + if (Character.isWhitespace(c)) { + return true; + } + return !(Character.isLetterOrDigit(c)) + && c != '_' + && extraNameCharacters().indexOf(c) == -1; + } - consoleReader.addCompleter(new BeeLineCompleter(this)); - return consoleReader; + private String extraNameCharacters() { + if (extraNameCharacters != null) { + return extraNameCharacters; + } + try { + extraNameCharacters = + getDatabaseMetaData() == null || getDatabaseMetaData().getExtraNameCharacters() == null ? "" + : getDatabaseMetaData().getExtraNameCharacters(); + return extraNameCharacters; + } catch (NoCurrentConnectionException noCurrentConnectionException) { + // this is not a problem at this point, will be tried again when a connection is present + debug("No current connection found while trying to retrieve extra name characters."); + return ""; + } catch (SQLException e) { + throw new RuntimeException("Error while retrieving database extra characters", e); + } + } + }; + // In JLine3, special characters (e.g., backslash) are handled by the terminal by default. + // This is not desired: we want to send the query string to HS2 exactly as entered, without interpretation. + parser.setEscapeChars(new char[]{}); + builder.parser(parser); + } + + private InputStream prepareInputStream(InputStream inputStream) { + if (inputStream != null) { + inputStream = new SequenceInputStream(inputStream, + new ByteArrayInputStream((new String("\n")).getBytes())); + } + return inputStream; + } + + protected Terminal buildTerminal(InputStream inputStream) throws IOException { + Terminal terminal; + if (inputStream != null) { // typically when there is a file script to read from + terminal = TerminalBuilder.builder().streams(inputStream, getErrorStream()).build(); + } else { // no input stream, normal operation: proper behavior needs a system terminal (which needs system streams) + terminal = TerminalBuilder.builder().system(true).dumb(false).streams(System.in, System.err).build(); + } + this.terminalsToClose.add(terminal); + return terminal; } void usage() { @@ -1496,13 +1605,12 @@ boolean dispatch(String line) { } line = HiveStringUtils.removeComments(line); + line = line.trim(); - if (line.trim().length() == 0) { + if (line.isEmpty()) { return true; } - line = line.trim(); - // save it to the current script, if any if (scriptOutputFile != null) { scriptOutputFile.addLine(line); @@ -2434,7 +2542,7 @@ Runnable getShutdownHook() { return shutdownHook; } - Completer getCommandCompletor() { + Completer getCommandCompleter() { return beeLineCommandCompleter; } @@ -2494,16 +2602,8 @@ PrintStream getErrorStream() { return errorStream; } - InputStream getInputStream() { - return inputStream; - } - - ConsoleReader getConsoleReader() { - return consoleReader; - } - - void setConsoleReader(ConsoleReader reader) { - this.consoleReader = reader; + public LineReader getLineReader() { + return lineReader; } List<String> getBatch() { diff --git a/beeline/src/java/org/apache/hive/beeline/BeeLineCommandCompleter.java b/beeline/src/java/org/apache/hive/beeline/BeeLineCommandCompleter.java index 44bfc9fa61b..8583b998af7 100644 --- a/beeline/src/java/org/apache/hive/beeline/BeeLineCommandCompleter.java +++ b/beeline/src/java/org/apache/hive/beeline/BeeLineCommandCompleter.java @@ -21,10 +21,10 @@ import java.util.LinkedList; import java.util.List; -import jline.console.completer.AggregateCompleter; -import jline.console.completer.Completer; -import jline.console.completer.NullCompleter; -import jline.console.completer.StringsCompleter; +import org.apache.hive.common.util.MatchingStringsCompleter; +import org.jline.reader.Completer; +import org.jline.reader.impl.completer.AggregateCompleter; +import org.jline.reader.impl.completer.NullCompleter; class BeeLineCommandCompleter extends AggregateCompleter { public BeeLineCommandCompleter(Iterable<CommandHandler> handlers) { @@ -32,21 +32,21 @@ public BeeLineCommandCompleter(Iterable<CommandHandler> handlers) { } public static List<Completer> getCompleters(Iterable<CommandHandler> handlers){ - List<Completer> completers = new LinkedList<Completer>(); + List<Completer> completers = new LinkedList<>(); for (CommandHandler handler : handlers) { String[] commandNames = handler.getNames(); if (commandNames != null) { for (String commandName : commandNames) { - List<Completer> compl = new LinkedList<Completer>(); - compl.add(new StringsCompleter(BeeLine.COMMAND_PREFIX + commandName)); + List<Completer> compl = new LinkedList<>(); + compl.add(new MatchingStringsCompleter(BeeLine.COMMAND_PREFIX + commandName)); compl.addAll(Arrays.asList(handler.getParameterCompleters())); compl.add(new NullCompleter()); // last param no complete - completers.add(new AggregateCompleter(compl.toArray(new Completer[compl.size()]))); + completers.add(new AggregateCompleter(compl.toArray(new Completer[0]))); } } } return completers; } -} \ No newline at end of file +} diff --git a/beeline/src/java/org/apache/hive/beeline/BeeLineCompleter.java b/beeline/src/java/org/apache/hive/beeline/BeeLineCompleter.java index 9213bcf7678..6b6b77c4bd1 100644 --- a/beeline/src/java/org/apache/hive/beeline/BeeLineCompleter.java +++ b/beeline/src/java/org/apache/hive/beeline/BeeLineCompleter.java @@ -24,10 +24,13 @@ import java.util.List; -import jline.console.completer.Completer; +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; /** - * Completor for BeeLine. It dispatches to sub-completors based on the + * Completer for BeeLine. It dispatches to sub-completors based on the * current arguments. * */ @@ -42,17 +45,15 @@ class BeeLineCompleter implements Completer { } @Override - public int complete(String buf, int pos, List cand) { - if (buf != null && buf.startsWith(BeeLine.COMMAND_PREFIX) - && !buf.startsWith(BeeLine.COMMAND_PREFIX + "all") - && !buf.startsWith(BeeLine.COMMAND_PREFIX + "sql")) { - return beeLine.getCommandCompletor().complete(buf, pos, cand); + public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) { + if (line != null && line.line().startsWith(BeeLine.COMMAND_PREFIX) + && !line.line().startsWith(BeeLine.COMMAND_PREFIX + "all") + && !line.line().startsWith(BeeLine.COMMAND_PREFIX + "sql")) { + beeLine.getCommandCompleter().complete(reader, line, candidates); } else { if (beeLine.getDatabaseConnection() != null && beeLine.getDatabaseConnection().getSQLCompleter() != null) { - return beeLine.getDatabaseConnection().getSQLCompleter().complete(buf, pos, cand); - } else { - return -1; + beeLine.getDatabaseConnection().getSQLCompleter().complete(reader, line, candidates); } } } -} \ No newline at end of file +} diff --git a/beeline/src/java/org/apache/hive/beeline/cli/HiveCli.java b/beeline/src/java/org/apache/hive/beeline/BeeLineDummyTerminal.java similarity index 53% copy from beeline/src/java/org/apache/hive/beeline/cli/HiveCli.java copy to beeline/src/java/org/apache/hive/beeline/BeeLineDummyTerminal.java index 99a72797592..1c03260472f 100644 --- a/beeline/src/java/org/apache/hive/beeline/cli/HiveCli.java +++ b/beeline/src/java/org/apache/hive/beeline/BeeLineDummyTerminal.java @@ -15,28 +15,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.hive.beeline.cli; -import org.apache.hadoop.util.ExitUtil; -import org.apache.hive.beeline.BeeLine; +package org.apache.hive.beeline; import java.io.IOException; import java.io.InputStream; -public class HiveCli { - private BeeLine beeLine; +import org.jline.terminal.Terminal; +import org.jline.terminal.impl.DumbTerminal; - public static void main(String[] args) throws IOException { - int status = new HiveCli().runWithArgs(args, null); - ExitUtil.terminate(status); +/** + * A Beeline implementation that always creates a DumbTerminal. + * This class resides in the production source code (not in tests) because Beeline can serve as a + * dummy terminal tool without real user interaction (e.g., HiveSchemaTool), not just in testing scenarios, + * although that is its primary use case. + */ +public class BeeLineDummyTerminal extends BeeLine { + + public BeeLineDummyTerminal() { + this(true); + } + + public BeeLineDummyTerminal(boolean isBeeLine) { + super(isBeeLine); } - public int runWithArgs(String[] cmd, InputStream inputStream) throws IOException { - beeLine = new BeeLine(false); - try { - return beeLine.begin(cmd, inputStream); - } finally { - beeLine.close(); - } + @Override + protected Terminal buildTerminal(InputStream inputStream) throws IOException { + return new DumbTerminal(inputStream, getErrorStream()); } } diff --git a/beeline/src/java/org/apache/hive/beeline/BeeLineOpts.java b/beeline/src/java/org/apache/hive/beeline/BeeLineOpts.java index 04ebab7df2e..fa97ebe13af 100644 --- a/beeline/src/java/org/apache/hive/beeline/BeeLineOpts.java +++ b/beeline/src/java/org/apache/hive/beeline/BeeLineOpts.java @@ -22,6 +22,15 @@ */ package org.apache.hive.beeline; +import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hive.common.util.MatchingStringsCompleter; +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; + import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -41,13 +50,6 @@ import java.util.Set; import java.util.TreeSet; -import jline.Terminal; -import jline.TerminalFactory; -import jline.console.completer.Completer; -import jline.console.completer.StringsCompleter; -import jline.console.history.MemoryHistory; -import org.apache.hadoop.hive.conf.HiveConf; - public class BeeLineOpts implements Completer { public static final int DEFAULT_MAX_WIDTH = 80; public static final int DEFAULT_MAX_HEIGHT = 80; @@ -86,7 +88,6 @@ public class BeeLineOpts implements Completer { private boolean showElapsedTime = true; private boolean entireLineAsCommand = false; private String numberFormat = "default"; - private final Terminal terminal = TerminalFactory.get(); private int maxWidth = DEFAULT_MAX_WIDTH; private int maxHeight = DEFAULT_MAX_HEIGHT; private int maxColumnWidth = DEFAULT_MAX_COLUMN_WIDTH; @@ -106,7 +107,7 @@ public class BeeLineOpts implements Completer { private final File rcFile = new File(saveDir(), "beeline.properties"); private String historyFile = new File(saveDir(), "history").getAbsolutePath(); - private int maxHistoryRows = MemoryHistory.DEFAULT_MAX_SIZE; + private int maxHistoryRows = 500; // as in MemoryHistory of JLine 2 private String scriptFile = null; private String[] initFiles = null; @@ -152,11 +153,17 @@ public String get(String envVar) { public BeeLineOpts(BeeLine beeLine, Properties props) { this.beeLine = beeLine; - if (terminal.getWidth() > 0) { - maxWidth = terminal.getWidth(); - } - if (terminal.getHeight() > 0) { - maxHeight = terminal.getHeight(); + try { + Terminal terminal = TerminalBuilder.terminal(); + if (terminal.getWidth() > 0) { + maxWidth = terminal.getWidth(); + } + if (terminal.getHeight() > 0) { + maxHeight = terminal.getHeight(); + } + terminal.close(); + } catch (IOException e) { + beeLine.debug("Failed to initialize terminal for max width/height check: " + e.getMessage()); } loadProperties(props); } @@ -195,12 +202,11 @@ public File saveDir() { @Override - public int complete(String buf, int pos, List cand) { + public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) { try { - return new StringsCompleter(propertyNames()).complete(buf, pos, cand); + new MatchingStringsCompleter(propertyNames()).complete(reader, line, candidates); } catch (Exception e) { beeLine.handleException(e); - return -1; } } @@ -742,4 +748,3 @@ public static void setEnv(Env envToUse){ env = envToUse; } } - diff --git a/beeline/src/java/org/apache/hive/beeline/BooleanCompleter.java b/beeline/src/java/org/apache/hive/beeline/BooleanCompleter.java index 6ab007dd05e..5295187789c 100644 --- a/beeline/src/java/org/apache/hive/beeline/BooleanCompleter.java +++ b/beeline/src/java/org/apache/hive/beeline/BooleanCompleter.java @@ -17,15 +17,14 @@ */ package org.apache.hive.beeline; -import jline.console.completer.StringsCompleter; +import org.apache.hive.common.util.MatchingStringsCompleter; /** - * JLine completor boolean value (true/false) + * JLine completer boolean value (true/false) */ -class BooleanCompleter extends StringsCompleter { +class BooleanCompleter extends MatchingStringsCompleter { public BooleanCompleter(){ - super(new String[] {"true", "false"}); + super("true", "false"); } - -} \ No newline at end of file +} diff --git a/beeline/src/java/org/apache/hive/beeline/CommandHandler.java b/beeline/src/java/org/apache/hive/beeline/CommandHandler.java index 18fcfc40b22..9a04649a9f5 100644 --- a/beeline/src/java/org/apache/hive/beeline/CommandHandler.java +++ b/beeline/src/java/org/apache/hive/beeline/CommandHandler.java @@ -22,7 +22,7 @@ */ package org.apache.hive.beeline; -import jline.console.completer.Completer; +import org.jline.reader.Completer; /** * A generic command to be executed. Execution of the command @@ -71,7 +71,7 @@ interface CommandHandler { /** - * Returns the completors that can handle parameters. + * Returns the completers that can handle parameters. */ public Completer[] getParameterCompleters(); @@ -80,4 +80,4 @@ interface CommandHandler { * @return */ public Throwable getLastException(); -} \ No newline at end of file +} diff --git a/beeline/src/java/org/apache/hive/beeline/Commands.java b/beeline/src/java/org/apache/hive/beeline/Commands.java index 42cc87c1bb6..7eaa2618f84 100644 --- a/beeline/src/java/org/apache/hive/beeline/Commands.java +++ b/beeline/src/java/org/apache/hive/beeline/Commands.java @@ -67,6 +67,8 @@ import org.apache.hive.jdbc.Utils; import org.apache.hive.jdbc.Utils.JdbcConnectionParams; import org.apache.hive.jdbc.logs.InPlaceUpdateStream; +import org.jline.reader.History; +import org.jline.reader.impl.LineReaderImpl; public class Commands { @@ -186,13 +188,9 @@ public boolean addlocaldriverjar(String line) { } public boolean history(String line) { - Iterator hist = beeLine.getConsoleReader().getHistory().entries(); - String[] tmp; - while(hist.hasNext()){ - tmp = hist.next().toString().split(":", 2); - tmp[0] = Integer.toString(Integer.parseInt(tmp[0]) + 1); - beeLine.output(beeLine.getColorBuffer().pad(tmp[0], 6) - .append(":" + tmp[1])); + for (History.Entry entry : beeLine.getLineReader().getHistory()) { + beeLine.output(beeLine.getColorBuffer().pad(Integer.toString(entry.index() + 1), 6) + .append(": " + entry.line())); } return true; } @@ -291,7 +289,7 @@ public boolean dropall(String line) { return beeLine.error(beeLine.loc("no-current-connection")); } try { - if (!(beeLine.getConsoleReader().readLine(beeLine.loc("really-drop-all")).equals("y"))) { + if (!(beeLine.getLineReader().readLine(beeLine.loc("really-drop-all")).equals("y"))) { return beeLine.error("abort-drop-all"); } @@ -1087,10 +1085,10 @@ private boolean showReport() { /* * Check if the input line is a multi-line command which needs to read further */ - public String handleMultiLineCmd(String line) throws IOException { + public String handleMultiLineCmd(String line) { line = HiveStringUtils.removeComments(line); Character mask = (System.getProperty("jline.terminal", "").equals("jline.UnsupportedTerminal")) ? null - : jline.console.ConsoleReader.NULL_MASK; + : LineReaderImpl.NULL_MASK; while (isMultiLine(line) && beeLine.getOpts().isAllowMultiLineCommand()) { StringBuilder prompt = new StringBuilder(beeLine.getPrompt()); @@ -1101,17 +1099,15 @@ public String handleMultiLineCmd(String line) throws IOException { } } } - String extra; + //avoid NPE below if for some reason -e argument has multi-line command - if (beeLine.getConsoleReader() == null) { + if (beeLine.getLineReader() == null) { throw new RuntimeException("Console reader not initialized. This could happen when there " + "is a multi-line command using -e option and which requires further reading from console"); } - if (beeLine.getOpts().isSilent() && beeLine.getOpts().getScriptFile() != null) { - extra = beeLine.getConsoleReader().readLine(null, mask); - } else { - extra = beeLine.getConsoleReader().readLine(prompt.toString()); - } + + String extra = (beeLine.getOpts().isSilent() && beeLine.getOpts().getScriptFile() != null) ? + beeLine.readLine(null, mask) : beeLine.readLine(prompt.toString(), null); if (extra == null) { //it happens when using -f and the line of cmds does not end with ; break; @@ -1663,12 +1659,11 @@ public boolean connect(Properties props) throws IOException { && !JdbcConnectionParams.AUTH_SSO_BROWSER_MODE.equals(auth)) { String urlForPrompt = url.substring(0, url.contains(";") ? url.indexOf(';') : url.length()); if (username == null) { - username = beeLine.getConsoleReader().readLine("Enter username for " + urlForPrompt + ": "); + username = beeLine.readLine("Enter username for " + urlForPrompt + ": ", null); } props.setProperty(JdbcConnectionParams.AUTH_USER, username); if (password == null) { - password = beeLine.getConsoleReader().readLine("Enter password for " + urlForPrompt + ": ", - new Character('*')); + password = beeLine.readLine("Enter password for " + urlForPrompt + ": ", '*'); } props.setProperty(JdbcConnectionParams.AUTH_PASSWD, password); } @@ -1963,7 +1958,7 @@ public boolean manual(String line) throws IOException { // silly little pager if (index % (beeLine.getOpts().getMaxHeight() - 1) == 0) { - String ret = beeLine.getConsoleReader().readLine(beeLine.loc("enter-for-more")); + String ret = beeLine.getLineReader().readLine(beeLine.loc("enter-for-more")); if (ret != null && ret.startsWith("q")) { break; } diff --git a/beeline/src/java/org/apache/hive/beeline/DatabaseConnection.java b/beeline/src/java/org/apache/hive/beeline/DatabaseConnection.java index 129fc2eb9cc..c3c5365631c 100644 --- a/beeline/src/java/org/apache/hive/beeline/DatabaseConnection.java +++ b/beeline/src/java/org/apache/hive/beeline/DatabaseConnection.java @@ -39,8 +39,8 @@ import org.apache.hive.jdbc.HiveConnection; -import jline.console.completer.ArgumentCompleter; -import jline.console.completer.Completer; +import org.jline.reader.Completer; +import org.jline.reader.impl.completer.ArgumentCompleter; class DatabaseConnection { private static final String HIVE_VAR_PREFIX = "hivevar:"; @@ -74,29 +74,8 @@ public String toString() { void setCompletions(boolean skipmeta) throws SQLException, IOException { - final String extraNameCharacters = - getDatabaseMetaData() == null || getDatabaseMetaData().getExtraNameCharacters() == null ? "" - : getDatabaseMetaData().getExtraNameCharacters(); - // setup the completer for the database - sqlCompleter = new ArgumentCompleter( - new ArgumentCompleter.AbstractArgumentDelimiter() { - // delimiters for SQL statements are any - // non-letter-or-number characters, except - // underscore and characters that are specified - // by the database to be valid name identifiers. - @Override - public boolean isDelimiterChar(CharSequence buffer, int pos) { - char c = buffer.charAt(pos); - if (Character.isWhitespace(c)) { - return true; - } - return !(Character.isLetterOrDigit(c)) - && c != '_' - && extraNameCharacters.indexOf(c) == -1; - } - }, - new SQLCompleter(SQLCompleter.getSQLCompleters(beeLine, skipmeta))); + sqlCompleter = new ArgumentCompleter(new SQLCompleter(SQLCompleter.getSQLCompleters(beeLine, skipmeta))); // not all argument elements need to hold true ((ArgumentCompleter) sqlCompleter).setStrict(false); } diff --git a/beeline/src/java/org/apache/hive/beeline/BooleanCompleter.java b/beeline/src/java/org/apache/hive/beeline/NoCurrentConnectionException.java similarity index 78% copy from beeline/src/java/org/apache/hive/beeline/BooleanCompleter.java copy to beeline/src/java/org/apache/hive/beeline/NoCurrentConnectionException.java index 6ab007dd05e..71117069fbd 100644 --- a/beeline/src/java/org/apache/hive/beeline/BooleanCompleter.java +++ b/beeline/src/java/org/apache/hive/beeline/NoCurrentConnectionException.java @@ -17,15 +17,8 @@ */ package org.apache.hive.beeline; -import jline.console.completer.StringsCompleter; - -/** - * JLine completor boolean value (true/false) - */ -class BooleanCompleter extends StringsCompleter { - - public BooleanCompleter(){ - super(new String[] {"true", "false"}); +public class NoCurrentConnectionException extends IllegalArgumentException { + public NoCurrentConnectionException(String message) { + super(message); } - -} \ No newline at end of file +} diff --git a/beeline/src/java/org/apache/hive/beeline/ReflectiveCommandHandler.java b/beeline/src/java/org/apache/hive/beeline/ReflectiveCommandHandler.java index a37ee891420..6fbda4c670d 100644 --- a/beeline/src/java/org/apache/hive/beeline/ReflectiveCommandHandler.java +++ b/beeline/src/java/org/apache/hive/beeline/ReflectiveCommandHandler.java @@ -22,9 +22,9 @@ */ package org.apache.hive.beeline; -import jline.console.completer.Completer; - import org.apache.hadoop.fs.shell.Command; +import org.jline.reader.Completer; + /** * A {@link Command} implementation that uses reflection to diff --git a/beeline/src/java/org/apache/hive/beeline/SQLCompleter.java b/beeline/src/java/org/apache/hive/beeline/SQLCompleter.java index b40b3a50636..bd5985ff301 100644 --- a/beeline/src/java/org/apache/hive/beeline/SQLCompleter.java +++ b/beeline/src/java/org/apache/hive/beeline/SQLCompleter.java @@ -30,11 +30,11 @@ import java.util.StringTokenizer; import java.util.TreeSet; -import jline.console.completer.StringsCompleter; +import org.apache.hive.common.util.MatchingStringsCompleter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class SQLCompleter extends StringsCompleter { +class SQLCompleter extends MatchingStringsCompleter { private static final Logger LOG = LoggerFactory.getLogger(SQLCompleter.class.getName()); diff --git a/beeline/src/java/org/apache/hive/beeline/TableNameCompletor.java b/beeline/src/java/org/apache/hive/beeline/TableNameCompleter.java similarity index 63% rename from beeline/src/java/org/apache/hive/beeline/TableNameCompletor.java rename to beeline/src/java/org/apache/hive/beeline/TableNameCompleter.java index 1eefe178826..96eec2e64ac 100644 --- a/beeline/src/java/org/apache/hive/beeline/TableNameCompletor.java +++ b/beeline/src/java/org/apache/hive/beeline/TableNameCompleter.java @@ -24,25 +24,28 @@ import java.util.List; -import jline.console.completer.Completer; -import jline.console.completer.StringsCompleter; +import org.apache.hive.common.util.MatchingStringsCompleter; +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; -class TableNameCompletor implements Completer { +class TableNameCompleter implements Completer { private final BeeLine beeLine; /** * @param beeLine */ - TableNameCompletor(BeeLine beeLine) { + TableNameCompleter(BeeLine beeLine) { this.beeLine = beeLine; } @Override - public int complete(String buf, int pos, List cand) { - if (beeLine.getDatabaseConnection() == null) { - return -1; + public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) { + final DatabaseConnection connection = beeLine.getDatabaseConnection(); + if (connection != null) { + new MatchingStringsCompleter(beeLine.getDatabaseConnection().getTableNames(true)) + .complete(reader, line, candidates); } - return new StringsCompleter(beeLine.getDatabaseConnection().getTableNames(true)) - .complete(buf, pos, cand); } -} \ No newline at end of file +} diff --git a/beeline/src/java/org/apache/hive/beeline/cli/HiveCli.java b/beeline/src/java/org/apache/hive/beeline/cli/HiveCli.java index 99a72797592..8edb16bc8b4 100644 --- a/beeline/src/java/org/apache/hive/beeline/cli/HiveCli.java +++ b/beeline/src/java/org/apache/hive/beeline/cli/HiveCli.java @@ -32,11 +32,15 @@ public static void main(String[] args) throws IOException { } public int runWithArgs(String[] cmd, InputStream inputStream) throws IOException { - beeLine = new BeeLine(false); + beeLine = createBeeline(); try { return beeLine.begin(cmd, inputStream); } finally { beeLine.close(); } } + + BeeLine createBeeline() { + return new BeeLine(false); + } } diff --git a/beeline/src/java/org/apache/hive/beeline/schematool/HiveSchemaTool.java b/beeline/src/java/org/apache/hive/beeline/schematool/HiveSchemaTool.java index 6a57763106e..22df6f0e8f5 100644 --- a/beeline/src/java/org/apache/hive/beeline/schematool/HiveSchemaTool.java +++ b/beeline/src/java/org/apache/hive/beeline/schematool/HiveSchemaTool.java @@ -32,6 +32,7 @@ import org.apache.hadoop.hive.metastore.tools.schematool.HiveSchemaHelper.NestedScriptParser; import org.apache.hadoop.util.ExitUtil; import org.apache.hive.beeline.BeeLine; +import org.apache.hive.beeline.BeeLineDummyTerminal; import org.apache.tez.dag.api.TezConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -105,7 +106,7 @@ protected void execSql(String sqlScriptFile) throws IOException { userName, passWord, sqlScriptFile); // run the script using Beeline - try (BeeLine beeLine = new BeeLine()) { + try (BeeLine beeLine = new BeeLineDummyTerminal()) { if (!verbose) { beeLine.setOutputStream(new PrintStream(new NullOutputStream())); beeLine.getOpts().setSilent(true); diff --git a/beeline/src/test/org/apache/hive/beeline/TestBeeLineHistory.java b/beeline/src/test/org/apache/hive/beeline/TestBeeLineHistory.java index c8f4d4e42e5..8f42e9ea699 100644 --- a/beeline/src/test/org/apache/hive/beeline/TestBeeLineHistory.java +++ b/beeline/src/test/org/apache/hive/beeline/TestBeeLineHistory.java @@ -56,13 +56,11 @@ public static void beforeTests() throws Exception { public void testNumHistories() throws Exception { ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream ops = new PrintStream(os); - BeeLine beeline = new BeeLine(); + BeeLine beeline = new BeeLineDummyTerminal(); beeline.getOpts().setHistoryFile(fileName); beeline.setOutputStream(ops); - Method method = beeline.getClass().getDeclaredMethod("setupHistory"); - method.setAccessible(true); - method.invoke(beeline); - beeline.initializeConsoleReader(null); + beeline.setupHistory(); + beeline.initializeLineReader(null); beeline.dispatch("!history"); String output = os.toString("UTF-8"); int numHistories = output.split("\n").length; @@ -74,13 +72,11 @@ public void testNumHistories() throws Exception { public void testHistory() throws Exception { ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream ops = new PrintStream(os); - BeeLine beeline = new BeeLine(); + BeeLine beeline = new BeeLineDummyTerminal(); beeline.getOpts().setHistoryFile(fileName); beeline.setOutputStream(ops); - Method method = beeline.getClass().getDeclaredMethod("setupHistory"); - method.setAccessible(true); - method.invoke(beeline); - beeline.initializeConsoleReader(null); + beeline.setupHistory(); + beeline.initializeLineReader(null); beeline.dispatch("!history"); String output = os.toString("UTF-8"); String[] tmp = output.split("\n"); diff --git a/beeline/src/test/org/apache/hive/beeline/TestCommands.java b/beeline/src/test/org/apache/hive/beeline/TestCommands.java index 2bd71b05a64..e926fd4b284 100644 --- a/beeline/src/test/org/apache/hive/beeline/TestCommands.java +++ b/beeline/src/test/org/apache/hive/beeline/TestCommands.java @@ -85,12 +85,14 @@ public void testLinesEndingWithComments() { */ @Test public void testBeelineCommands() throws IOException { - // avoid System.exit() call in beeline which causes JVM to exit and fails the test + // avoid System.exit() call in beeline which causes JVM to exit and fails the test System.setProperty(BeeLineOpts.PROPERTY_NAME_EXIT, "true"); // Verify the command without ';' at the end also works fine - BeeLine.mainWithInputRedirection(new String[] {"-u", "jdbc:hive2://", "-e", "select 3"}, null); + BeeLine.mainWithInputRedirection(new String[] {"-u", "jdbc:hive2://", "-e", "select 3"}, null, + new BeeLineDummyTerminal()); BeeLine.mainWithInputRedirection( - new String[] {"-u", "jdbc:hive2://", "-e", "create table t1(x int); show tables"}, null); + new String[] {"-u", "jdbc:hive2://", "-e", "create table t1(x int); show tables"}, null, + new BeeLineDummyTerminal()); } /** diff --git a/beeline/src/java/org/apache/hive/beeline/BooleanCompleter.java b/beeline/src/test/org/apache/hive/beeline/cli/HiveCliForTest.java similarity index 75% copy from beeline/src/java/org/apache/hive/beeline/BooleanCompleter.java copy to beeline/src/test/org/apache/hive/beeline/cli/HiveCliForTest.java index 6ab007dd05e..60e68d4b75d 100644 --- a/beeline/src/java/org/apache/hive/beeline/BooleanCompleter.java +++ b/beeline/src/test/org/apache/hive/beeline/cli/HiveCliForTest.java @@ -15,17 +15,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.hive.beeline; +package org.apache.hive.beeline.cli; -import jline.console.completer.StringsCompleter; +import org.apache.hive.beeline.BeeLine; +import org.apache.hive.beeline.BeeLineDummyTerminal; -/** - * JLine completor boolean value (true/false) - */ -class BooleanCompleter extends StringsCompleter { +public class HiveCliForTest extends HiveCli { - public BooleanCompleter(){ - super(new String[] {"true", "false"}); + @Override + BeeLine createBeeline() { + return new BeeLineDummyTerminal(false); } - -} \ No newline at end of file +} diff --git a/beeline/src/test/org/apache/hive/beeline/cli/TestHiveCli.java b/beeline/src/test/org/apache/hive/beeline/cli/TestHiveCli.java index a8378d91435..89bf93672ed 100644 --- a/beeline/src/test/org/apache/hive/beeline/cli/TestHiveCli.java +++ b/beeline/src/test/org/apache/hive/beeline/cli/TestHiveCli.java @@ -36,6 +36,7 @@ import java.io.OutputStream; import java.io.PrintStream; import java.net.URISyntaxException; +import java.nio.charset.Charset; public class TestHiveCli { private static final Logger LOG = LoggerFactory.getLogger(TestHiveCli.class.getName()); @@ -248,7 +249,7 @@ private void executeCMD(String[] args, String input, int retCode) { int ret = 0; try { if (input != null) { - inputStream = IOUtils.toInputStream(input); + inputStream = IOUtils.toInputStream(input, Charset.defaultCharset()); } ret = cli.runWithArgs(args, inputStream); } catch (Throwable e) { @@ -270,11 +271,11 @@ private void verifyCMD(String CMD, String keywords, OutputStream os, String[] op String output = os.toString(); LOG.debug(output); if (contains) { - Assert.assertTrue("The expected keyword \"" + keywords + "\" occur in the output: " + output, + Assert.assertTrue("The expected keyword \"" + keywords + "\" should appear in the output: " + output, output.contains(keywords)); } else { Assert.assertFalse( - "The expected keyword \"" + keywords + "\" should be excluded occurred in the output: " + "The expected keyword \"" + keywords + "\" should not appear in the output: " + output, output.contains(keywords)); } } @@ -293,9 +294,9 @@ public static void init(){ @Before public void setup() throws IOException, URISyntaxException { System.setProperty("datanucleus.schema.autoCreateAll", "true"); - cli = new HiveCli(); - initFromFile(); + cli = new HiveCliForTest(); redirectOutputStream(); + initFromFile(); } private void redirectOutputStream() { diff --git a/cli/pom.xml b/cli/pom.xml index defc5fbefd9..ebb0fbff8c5 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -82,7 +82,7 @@ <scope>test</scope> </dependency> <dependency> - <groupId>jline</groupId> + <groupId>org.jline</groupId> <artifactId>jline</artifactId> </dependency> <dependency> diff --git a/cli/src/java/org/apache/hadoop/hive/cli/CliDriver.java b/cli/src/java/org/apache/hadoop/hive/cli/CliDriver.java index 4bff8082031..4cef3b5d657 100644 --- a/cli/src/java/org/apache/hadoop/hive/cli/CliDriver.java +++ b/cli/src/java/org/apache/hadoop/hive/cli/CliDriver.java @@ -16,7 +16,7 @@ * limitations under the License. */ -package org.apache.hadoop.hive.cli; + package org.apache.hadoop.hive.cli; import static org.apache.hadoop.hive.shims.HadoopShims.USER_ID; import static org.apache.hadoop.util.StringUtils.stringifyException; @@ -74,25 +74,26 @@ import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.util.ExitUtil; import org.apache.hive.common.util.HiveStringUtils; +import org.apache.hive.common.util.MatchingStringsCompleter; import org.apache.hive.common.util.ShutdownHookManager; +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.History; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.ParsedLine; +import org.jline.reader.impl.DefaultParser; +import org.jline.reader.impl.completer.ArgumentCompleter; +import org.jline.reader.impl.history.DefaultHistory; +import org.jline.terminal.Terminal; +import org.jline.terminal.Terminal.Signal; +import org.jline.terminal.Terminal.SignalHandler; +import org.jline.terminal.TerminalBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Splitter; -import jline.console.ConsoleReader; -import jline.console.completer.ArgumentCompleter; -import jline.console.completer.ArgumentCompleter.AbstractArgumentDelimiter; -import jline.console.completer.ArgumentCompleter.ArgumentDelimiter; -import jline.console.completer.Completer; -import jline.console.completer.StringsCompleter; -import jline.console.history.FileHistory; -import jline.console.history.History; -import jline.console.history.PersistentHistory; -import sun.misc.Signal; -import sun.misc.SignalHandler; - - /** * CliDriver. * @@ -107,7 +108,7 @@ public class CliDriver { public static final String HIVERCFILE = ".hiverc"; private final LogHelper console; - protected ConsoleReader reader; + protected LineReader reader; private Configuration conf; public CliDriver() { @@ -374,8 +375,8 @@ public CommandProcessorResponse processLine(String line, boolean allowInterrupti if (allowInterrupting) { // Remember all threads that were running at the time we started line processing. // Hook up the custom Ctrl+C handler while processing this line - interruptSignal = new Signal("INT"); - oldSignal = Signal.handle(interruptSignal, new SignalHandler() { + interruptSignal = Terminal.Signal.INT; + oldSignal = reader.getTerminal().handle(interruptSignal, new SignalHandler() { private boolean interruptRequested; @Override @@ -437,8 +438,8 @@ public void handle(Signal signal) { return lastRet; } finally { // Once we are done processing the line, restore the old handler - if (oldSignal != null && interruptSignal != null) { - Signal.handle(interruptSignal, oldSignal); + if (oldSignal != null) { + reader.getTerminal().handle(interruptSignal, oldSignal); } } } @@ -590,7 +591,7 @@ public void processSelectDatabase(CliSessionState ss) throws IOException, Comman public static Completer[] getCommandCompleter() { // StringsCompleter matches against a pre-defined wordlist // We start with an empty wordlist and build it up - List<String> candidateStrings = new ArrayList<String>(); + List<String> candidateStrings = new ArrayList<>(); // We add Hive function names // For functions that aren't infix operators, we add an open @@ -609,47 +610,32 @@ public static Completer[] getCommandCompleter() { candidateStrings.add(s.toLowerCase()); } - StringsCompleter strCompleter = new StringsCompleter(candidateStrings); - - // Because we use parentheses in addition to whitespace - // as a keyword delimiter, we need to define a new ArgumentDelimiter - // that recognizes parenthesis as a delimiter. - ArgumentDelimiter delim = new AbstractArgumentDelimiter() { - @Override - public boolean isDelimiterChar(CharSequence buffer, int pos) { - char c = buffer.charAt(pos); - return (Character.isWhitespace(c) || c == '(' || c == ')' || - c == '[' || c == ']'); - } - }; + Completer strCompleter = new MatchingStringsCompleter(candidateStrings); - // The ArgumentCompletor allows us to match multiple tokens + // The ArgumentCompleter allows us to match multiple tokens // in the same line. - final ArgumentCompleter argCompleter = new ArgumentCompleter(delim, strCompleter); - // By default ArgumentCompletor is in "strict" mode meaning + final ArgumentCompleter argCompleter = new ArgumentCompleter(strCompleter); + // By default ArgumentCompleter is in "strict" mode meaning // a token is only auto-completed if all prior tokens // match. We don't want that since there are valid tokens // that are not in our wordlist (eg. table and column names) argCompleter.setStrict(false); - // ArgumentCompletor always adds a space after a matched token. + // ArgumentCompleter always adds a space after a matched token. // This is undesirable for function names because a space after // the opening parenthesis is unnecessary (and uncommon) in Hive. - // We stack a custom Completor on top of our ArgumentCompletor + // We stack a custom Completer on top of our ArgumentCompleter // to reverse this. - Completer customCompletor = new Completer () { - @Override - public int complete (String buffer, int offset, List completions) { - List<String> comp = completions; - int ret = argCompleter.complete(buffer, offset, completions); - // ConsoleReader will do the substitution if and only if there - // is exactly one valid completion, so we ignore other cases. - if (completions.size() == 1) { - if (comp.get(0).endsWith("( ")) { - comp.set(0, comp.get(0).trim()); - } + Completer customCompleter = (reader, line, candidates) -> { + argCompleter.complete(reader, line, candidates); + candidates.forEach(System.out::println); + // ConsoleReader will do the substitution if and only if there + // is exactly one valid completion, so we ignore other cases. + if (candidates.size() == 1) { + String candidateStr = candidates.get(0).value(); + if (candidateStr.endsWith("( ")) { + candidates.set(0, new Candidate(candidateStr.trim())); } - return ret; } }; @@ -658,64 +644,58 @@ public int complete (String buffer, int offset, List completions) { vars.add(conf.varname); } - StringsCompleter confCompleter = new StringsCompleter(vars) { + Completer confCompleter = new MatchingStringsCompleter(vars) { @Override - public int complete(final String buffer, final int cursor, final List<CharSequence> clist) { - int result = super.complete(buffer, cursor, clist); - if (clist.isEmpty() && cursor > 1 && buffer.charAt(cursor - 1) == '=') { - HiveConf.ConfVars var = HiveConf.getConfVars(buffer.substring(0, cursor - 1)); - if (var == null) { - return result; + public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) { + super.complete(reader, line, candidates); + final int cursor = line.cursor(); + if (candidates.isEmpty() && cursor > 1 && line.word().charAt(cursor - 1) == '=') { + HiveConf.ConfVars confVars = HiveConf.getConfVars(line.word().substring(0, cursor - 1)); + if (confVars == null) { + return; } - if (var.getValidator() instanceof Validator.StringSet) { - Validator.StringSet validator = (Validator.StringSet)var.getValidator(); - clist.addAll(validator.getExpected()); - } else if (var.getValidator() != null) { - clist.addAll(Arrays.asList(var.getValidator().toDescription(), "")); + if (confVars.getValidator() instanceof Validator.StringSet) { + Validator.StringSet validator = (Validator.StringSet)confVars.getValidator(); + validator.getExpected().stream().map(Candidate::new).forEach(candidates::add); + } else if (confVars.getValidator() != null) { + candidates.add(new Candidate(confVars.getValidator().toDescription())); } else { - clist.addAll(Arrays.asList("Expects " + var.typeString() + " type value", "")); + candidates.add(new Candidate("Expects " + confVars.typeString() + " type value")); } - return cursor; + return; } - if (clist.size() > DELIMITED_CANDIDATE_THRESHOLD) { - Set<CharSequence> delimited = new LinkedHashSet<CharSequence>(); - for (CharSequence candidate : clist) { + if (candidates.size() > DELIMITED_CANDIDATE_THRESHOLD) { + Set<Candidate> delimited = new LinkedHashSet<>(); + for (Candidate candidate : candidates) { Iterator<String> it = Splitter.on(".").split( - candidate.subSequence(cursor, candidate.length())).iterator(); + candidate.value().subSequence(cursor, candidate.value().length())).iterator(); if (it.hasNext()) { String next = it.next(); if (next.isEmpty()) { next = "."; } - candidate = buffer != null ? buffer.substring(0, cursor) + next : next; + candidate = new Candidate(line.line() != null ? line.line().substring(0, cursor) + next : next); } delimited.add(candidate); } - clist.clear(); - clist.addAll(delimited); + candidates.clear(); + candidates.addAll(delimited); } - return result; } }; - StringsCompleter setCompleter = new StringsCompleter("set") { - @Override - public int complete(String buffer, int cursor, List<CharSequence> clist) { - return buffer != null && buffer.equals("set") ? super.complete(buffer, cursor, clist) : -1; - } - }; + Completer setCompleter = new MatchingStringsCompleter("set"); ArgumentCompleter propCompleter = new ArgumentCompleter(setCompleter, confCompleter) { @Override - public int complete(String buffer, int offset, List<CharSequence> completions) { - int ret = super.complete(buffer, offset, completions); - if (completions.size() == 1) { - completions.set(0, ((String)completions.get(0)).trim()); + public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) { + super.complete(reader, line, candidates); + if (candidates.size() == 1) { + candidates.set(0, new Candidate(candidates.get(0).value().trim())); } - return ret; } }; - return new Completer[] {propCompleter, customCompletor}; + return new Completer[] {propCompleter, customCompleter}; } public static void main(String[] args) throws Exception { @@ -831,7 +811,7 @@ protected HiveConf getConf() { private CommandProcessorResponse executeDriver(CliSessionState ss, HiveConf conf, OptionsProcessor oproc) throws Exception { - CliDriver cli = new CliDriver(); + CliDriver cli = newCliDriver(); cli.setHiveVariables(oproc.getHiveVariables()); // use the specified database if specified @@ -856,7 +836,7 @@ private CommandProcessorResponse executeDriver(CliSessionState ss, HiveConf conf console.printInfo(HiveConf.generateMrDeprecationWarning()); } - setupConsoleReader(); + setupLineReader(); String line; CommandProcessorResponse response = new CommandProcessorResponse(); @@ -889,15 +869,16 @@ private CommandProcessorResponse executeDriver(CliSessionState ss, HiveConf conf return response; } - private void setupCmdHistory() { + protected CliDriver newCliDriver() { + return new CliDriver(); + } + + private String setupCmdHistory() { final String HISTORYFILE = ".hivehistory"; String historyDirectory = System.getProperty("user.home"); - PersistentHistory history = null; try { if ((new File(historyDirectory)).exists()) { - String historyFile = historyDirectory + File.separator + HISTORYFILE; - history = new FileHistory(new File(historyFile)); - reader.setHistory(history); + return historyDirectory + File.separator + HISTORYFILE; } else { System.err.println("WARNING: Directory for Hive history file: " + historyDirectory + " does not exist. History will not be available during this session."); @@ -906,32 +887,48 @@ private void setupCmdHistory() { System.err.println("WARNING: Encountered an error while trying to initialize Hive's " + "history file. History will not be available during this session."); System.err.println(e.getMessage()); + return null; } // add shutdown hook to flush the history to history file - ShutdownHookManager.addShutdownHook(new Runnable() { - @Override - public void run() { - History h = reader.getHistory(); - if (h instanceof FileHistory) { - try { - ((FileHistory) h).flush(); - } catch (IOException e) { - System.err.println("WARNING: Failed to write command history file: " + e.getMessage()); - } - } + ShutdownHookManager.addShutdownHook(() -> { + History h = reader.getHistory(); + try { + h.save(); + } catch (IOException e) { + System.err.println("WARNING: Failed to write command history file: " + e.getMessage()); } }); + return null; } - protected void setupConsoleReader() throws IOException { - reader = new ConsoleReader(); - reader.setExpandEvents(false); - reader.setBellEnabled(false); - for (Completer completer : getCommandCompleter()) { - reader.addCompleter(completer); + protected void setupLineReader() throws IOException { + LineReaderBuilder builder = LineReaderBuilder.builder(); + builder.variable(LineReader.BELL_STYLE, "audible"); + Arrays.stream(getCommandCompleter()).forEach(builder::completer); + builder.terminal(TerminalBuilder.terminal()); + builder.parser(getDefaultParser()); + builder.history(new DefaultHistory()); + + String historyFile = setupCmdHistory(); + if (historyFile != null) { + builder.variable(LineReader.HISTORY_FILE, historyFile); } - setupCmdHistory(); + reader = builder.build(); + } + + static DefaultParser getDefaultParser() { + return new DefaultParser() { + @Override + public boolean isDelimiterChar(CharSequence buffer, int pos) { + // Because we use parentheses in addition to whitespace + // as a keyword delimiter, we need to define a new ArgumentDelimiter + // that recognizes parenthesis as a delimiter. + final char c = buffer.charAt(pos); + return (Character.isWhitespace(c) || c == '(' || c == ')' || + c == '[' || c == ']'); + } + }; } /** @@ -971,5 +968,4 @@ private static String spacesForString(String s) { public void setHiveVariables(Map<String, String> hiveVariables) { SessionState.get().setHiveVariables(hiveVariables); } - } diff --git a/cli/src/test/org/apache/hadoop/hive/cli/TestCliDriverMethods.java b/cli/src/test/org/apache/hadoop/hive/cli/TestCliDriverMethods.java index e22790f54d0..eade17d66fc 100644 --- a/cli/src/test/org/apache/hadoop/hive/cli/TestCliDriverMethods.java +++ b/cli/src/test/org/apache/hadoop/hive/cli/TestCliDriverMethods.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileWriter; @@ -39,11 +40,6 @@ import java.util.List; import java.util.Map; -import jline.console.ConsoleReader; -import jline.console.completer.ArgumentCompleter; -import jline.console.completer.Completer; - - import org.apache.commons.io.FileUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.common.io.SessionStream; @@ -57,7 +53,11 @@ import org.apache.hadoop.hive.ql.processors.CommandProcessorException; import org.apache.hadoop.hive.ql.processors.CommandProcessorResponse; import org.apache.hadoop.util.ExitUtil; - +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.impl.LineReaderImpl; +import org.jline.reader.impl.completer.ArgumentCompleter; +import org.jline.terminal.impl.DumbTerminal; import org.junit.Test; import static org.junit.Assert.assertTrue; @@ -148,7 +148,7 @@ public void testThatCliDriverDoesNotStripComments() throws Exception { CliDriver cliDriver = new CliDriver(); // issue a command with bad options cliDriver.processCmd("!ls --abcdefghijklmnopqrstuvwxyz123456789"); - assertTrue("Comments with '--; should not have been stripped, so command should fail", false); + fail("Comments with '--; should not have been stripped, so command should fail"); } catch (CommandProcessorException e) { // this is expected to happen } finally { @@ -171,8 +171,6 @@ public void testThatCliDriverDoesNotStripComments() throws Exception { * Schema to throw against test * @return Output that would have been sent to the user * @throws CommandProcessorException - * @throws CommandNeedRetryException - * won't actually be thrown */ private PrintStream headerPrintingTestDriver(Schema mockSchema) throws CommandProcessorException { CliDriver cliDriver = new CliDriver(); @@ -205,23 +203,25 @@ private PrintStream headerPrintingTestDriver(Schema mockSchema) throws CommandPr @Test - public void testGetCommandCompletor() { - Completer[] completors = CliDriver.getCommandCompleter(); - assertEquals(2, completors.length); - assertTrue(completors[0] instanceof ArgumentCompleter); - assertTrue(completors[1] instanceof Completer); - - List<CharSequence> testList = Arrays.asList(")"); - completors[1].complete("fdsdfsdf", 0, testList); - assertEquals(")", testList.get(0)); - testList=new ArrayList<CharSequence>(); - completors[1].complete("len", 0, testList); - assertTrue(testList.get(0).toString().endsWith("length(")); - - testList=new ArrayList<CharSequence>(); - completors[0].complete("set f", 0, testList); - assertEquals("set", testList.get(0)); - + public void testGetCommandCompleter() { + Completer[] completers = CliDriver.getCommandCompleter(); + assertEquals(2, completers.length); + assertTrue(completers[0] instanceof ArgumentCompleter); + assertNotNull(completers[1]); + + final List<Candidate> candidates1 = new ArrayList<>(); + candidates1.add(new Candidate(")")); + completers[1].complete(null, CliDriver.getDefaultParser().parse("fdsdfsdf", 0), candidates1); + assertEquals(")", candidates1.getFirst().value()); + + final List<Candidate> candidates2 = new ArrayList<>(); + completers[1].complete(null, CliDriver.getDefaultParser().parse("length", 0), candidates2); + System.out.printf("--- --> %s%n", candidates2.getFirst().value()); + assertTrue(candidates2.getFirst().value().endsWith("length(")); + + final List<Candidate> candidates3 = new ArrayList<>(); + completers[0].complete(null, CliDriver.getDefaultParser().parse("set f", 0), candidates3); + assertEquals("set", candidates3.getFirst().value()); } @Test @@ -257,7 +257,6 @@ public void testRun() throws Exception { System.setErr(oldErr); } - } /** @@ -386,21 +385,21 @@ public void testprocessInitFiles() throws Exception { public void testCommandSplits() { // Test double quote in the string String cmd1 = "insert into escape1 partition (ds='1', part='\"') values (\"!\")"; - assertEquals(cmd1, CliDriver.splitSemiColon(cmd1).get(0)); - assertEquals(cmd1, CliDriver.splitSemiColon(cmd1 + ";").get(0)); + assertEquals(cmd1, CliDriver.splitSemiColon(cmd1).getFirst()); + assertEquals(cmd1, CliDriver.splitSemiColon(cmd1 + ";").getFirst()); // Test escape String cmd2 = "insert into escape1 partition (ds='1', part='\"\\'') values (\"!\")"; - assertEquals(cmd2, CliDriver.splitSemiColon(cmd2).get(0)); - assertEquals(cmd2, CliDriver.splitSemiColon(cmd2 + ";").get(0)); + assertEquals(cmd2, CliDriver.splitSemiColon(cmd2).getFirst()); + assertEquals(cmd2, CliDriver.splitSemiColon(cmd2 + ";").getFirst()); // Test multiple commands List<String> results = CliDriver.splitSemiColon(cmd1 + ";" + cmd2); - assertEquals(cmd1, results.get(0)); + assertEquals(cmd1, results.getFirst()); assertEquals(cmd2, results.get(1)); results = CliDriver.splitSemiColon(cmd1 + ";" + cmd2 + ";"); - assertEquals(cmd1, results.get(0)); + assertEquals(cmd1, results.getFirst()); assertEquals(cmd2, results.get(1)); } @@ -423,67 +422,72 @@ private static void setEnv(String key, String value) throws Exception { } private static class FakeCliDriver extends CliDriver { - - private HiveConf conf; - - public FakeCliDriver(HiveConf configuration) { - this.conf = configuration; - } - - @Override - protected void setupConsoleReader() throws IOException { - reader = new FakeConsoleReader(); + private final HiveConf conf; + + public FakeCliDriver(HiveConf conf) throws IOException { + this.conf = conf; + + reader = new LineReaderImpl(new DumbTerminal(new ByteArrayInputStream(new byte[0]), System.err)) { + + File temp = null; + private int counter = 0; + + @Override + public String readLine(String prompt) { + FileWriter writer; + try { + switch (counter++) { + case 0: + return "!echo test message;"; + case 1: + temp = File.createTempFile("hive", "test"); + temp.deleteOnExit(); + return "source " + temp.getAbsolutePath() + ";"; + case 2: + temp = File.createTempFile("hive", "test"); + temp.deleteOnExit(); + writer = new FileWriter(temp); + writer.write("bla bla bla"); + writer.close(); + return "list file file://" + temp.getAbsolutePath() + ";"; + case 3: + return "!echo "; + case 4: + return "test message;"; + case 5: + return "source fakeFile;"; + case 6: + temp = File.createTempFile("hive", "test"); + temp.deleteOnExit(); + writer = new FileWriter(temp); + writer.write("source fakeFile;"); + writer.close(); + return "list file file://" + temp.getAbsolutePath() + ";"; + default: + return null; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; } protected HiveConf getConf() { return conf; } - } - - private static class FakeConsoleReader extends ConsoleReader { - private int counter = 0; - File temp = null; - - public FakeConsoleReader() throws IOException { - super(); + @Override + protected void setupLineReader() { + // NO-OP: let's use the reader created early in the constructor to prevent NPEs } @Override - public String readLine(String prompt) throws IOException { - FileWriter writer; - switch (counter++) { - case 0: - return "!echo test message;"; - case 1: - temp = File.createTempFile("hive", "test"); - temp.deleteOnExit(); - return "source " + temp.getAbsolutePath() + ";"; - case 2: - temp = File.createTempFile("hive", "test"); - temp.deleteOnExit(); - writer = new FileWriter(temp); - writer.write("bla bla bla"); - writer.close(); - return "list file file://" + temp.getAbsolutePath() + ";"; - case 3: - return "!echo "; - case 4: - return "test message;"; - case 5: - return "source fakeFile;"; - case 6: - temp = File.createTempFile("hive", "test"); - temp.deleteOnExit(); - writer = new FileWriter(temp); - writer.write("source fakeFile;"); - writer.close(); - return "list file file://" + temp.getAbsolutePath() + ";"; - - - // drop table over10k; - default: - return null; + protected CliDriver newCliDriver() { + try { + return new FakeCliDriver(conf); + } catch (IOException e) { + throw new RuntimeException(e); } } } diff --git a/common/pom.xml b/common/pom.xml index c1ee4ffc126..66931be0c03 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -82,7 +82,7 @@ </exclusions> </dependency> <dependency> - <groupId>jline</groupId> + <groupId>org.jline</groupId> <artifactId>jline</artifactId> </dependency> <dependency> @@ -246,7 +246,6 @@ <dependency> <groupId>org.fusesource.jansi</groupId> <artifactId>jansi</artifactId> - <version>${jansi.version}</version> </dependency> <!-- test inter-project --> <dependency> diff --git a/common/src/java/org/apache/hive/common/util/MatchingStringsCompleter.java b/common/src/java/org/apache/hive/common/util/MatchingStringsCompleter.java new file mode 100644 index 00000000000..abe7d17f6d8 --- /dev/null +++ b/common/src/java/org/apache/hive/common/util/MatchingStringsCompleter.java @@ -0,0 +1,70 @@ +/* + * 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.hive.common.util; + +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * A matching string Completer based on JLine's StringCompleter + */ +public class MatchingStringsCompleter implements Completer { + protected SortedSet<String> candidateStrings = new TreeSet<>(); + + public MatchingStringsCompleter() { + // empty + } + + public MatchingStringsCompleter(String... strings) { + this(Arrays.asList(strings)); + } + + public MatchingStringsCompleter(Iterable<String> strings) { + strings.forEach(candidateStrings::add); + } + + public Collection<String> getStrings() { + return candidateStrings; + } + + @Override + public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) { + Objects.requireNonNull(candidates, "candidates must not be null"); + + if (line == null) { + candidateStrings.stream().map(Candidate::new).forEach(candidates::add); + } else { + for (String match : this.candidateStrings.tailSet(line.word())) { + if (!match.startsWith(line.word())) { + break; + } + candidates.add(new Candidate(match)); + } + } + } +} diff --git a/hcatalog/hcatalog-pig-adapter/pom.xml b/hcatalog/hcatalog-pig-adapter/pom.xml index 1888a0a488a..f5062369ebd 100644 --- a/hcatalog/hcatalog-pig-adapter/pom.xml +++ b/hcatalog/hcatalog-pig-adapter/pom.xml @@ -30,6 +30,8 @@ <name>Hive HCatalog Pig Adapter</name> <properties> <hive.path.to.root>../..</hive.path.to.root> + <!-- HIVE-28992: only upgrade to newer than 3.25.0 if you tested the prompt --> + <jline.version>3.25.0</jline.version> </properties> <dependencies> <!-- dependencies are always listed in sorted order by groupId, artifactId --> @@ -123,9 +125,9 @@ <scope>test</scope> </dependency> <dependency> - <groupId>jline</groupId> + <groupId>org.jline</groupId> <artifactId>jline</artifactId> - <version>0.9.94</version> + <version>${jline.version}</version> <scope>test</scope> </dependency> <dependency> diff --git a/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeeLineWithArgs.java b/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeeLineWithArgs.java index 08626809fb8..92f2d8c5657 100644 --- a/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeeLineWithArgs.java +++ b/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeeLineWithArgs.java @@ -24,11 +24,12 @@ import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; -import java.io.StringBufferInputStream; import java.io.UnsupportedEncodingException; import java.sql.Connection; import java.sql.DriverManager; @@ -53,6 +54,8 @@ import org.apache.hive.jdbc.Utils; import org.apache.hive.jdbc.miniHS2.MiniHS2; import org.apache.hive.jdbc.miniHS2.MiniHS2.MiniClusterType; +import org.jline.terminal.Terminal; +import org.jline.terminal.impl.DumbTerminal; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -158,10 +161,9 @@ public static void postTests() { * @return The stderr and stdout from running the script * @throws Throwable */ - static String testCommandLineScript(List<String> argList, InputStream inputStream, - OutStream streamType) + static String testCommandLineScript(List<String> argList, OutStream streamType) throws Throwable { - BeeLine beeLine = new BeeLine(); + BeeLine beeLine = getBeeLineDummyTerminal(); ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream beelineOutputStream = new PrintStream(os); switch (streamType) { @@ -175,13 +177,30 @@ static String testCommandLineScript(List<String> argList, InputStream inputStrea throw new RuntimeException("Unexpected outstream type " + streamType); } String[] args = argList.toArray(new String[argList.size()]); - beeLine.begin(args, inputStream); + beeLine.begin(args, null); beeLine.close(); beelineOutputStream.close(); String output = os.toString("UTF8"); return output; } + private static BeeLineDummyTerminal getBeeLineDummyTerminal() { + return new BeeLineDummyTerminal() { + /* + * Unit tests in this class call the begin function with a null input stream. Once the initial (-i) and file (-f) + * scripts are completed, the flow enters the interactive code path—which we want to skip in tests to allow them + * to exit cleanly. In the real world, this would lead to the interactive Beeline shell, but for testing purposes, + * we bypass that by providing a dummy empty stream. This avoids infinite loops or null pointer errors in the + * JLine read logic. + */ + @Override + protected Terminal buildTerminal(InputStream inputStream) throws IOException { + return new DumbTerminal(inputStream == null ? new ByteArrayInputStream("".getBytes()) : inputStream, + getErrorStream()); + } + }; + } + /** * Attempt to execute a simple script file with the -f and -i option to * BeeLine to test for presence of an expected pattern in the output (stdout @@ -266,8 +285,8 @@ public Tuple<Pattern> apply(Tuple<String> tuple) { boolean matches = m.matches(); if (patternToMatch.shouldMatch != matches) { //failed - fail("Output" + output + " should" + (patternToMatch.shouldMatch ? "" : " not") + - " contain " + patternToMatch.pattern.pattern()); + fail(String.format("Output (mode: %s) '%s' should %s contain '%s'", mode, output, + (patternToMatch.shouldMatch ? "" : " not"), patternToMatch.pattern.pattern())); } } } @@ -281,18 +300,16 @@ enum Modes { INIT { @Override String output(File scriptFile, List<String> argList, OutStream streamType) throws Throwable { - List<String> copy = new ArrayList<>(argList); - copy.add("-i"); - copy.add(scriptFile.getAbsolutePath()); - return testCommandLineScript(copy, new StringBufferInputStream("!quit\n"), streamType); + List<String> finalArgs = new ArrayList<>(argList); + finalArgs.addAll(Arrays.asList("-i", scriptFile.getAbsolutePath())); + return testCommandLineScript(finalArgs, streamType); } }, SCRIPT { @Override String output(File scriptFile, List<String> argList, OutStream streamType) throws Throwable { - List<String> copy = new ArrayList<>(argList); - copy.add("-f"); - copy.add(scriptFile.getAbsolutePath()); - return testCommandLineScript(copy, null, streamType); + List<String> finalArgs = new ArrayList<>(argList); + finalArgs.addAll(Arrays.asList("-f", scriptFile.getAbsolutePath())); + return testCommandLineScript(finalArgs, streamType); } }; @@ -314,11 +331,10 @@ abstract String output(File scriptFile, List<String> argList, OutStream streamTy private void testCommandEnclosedQuery(String enclosedQuery, String expectedPattern, boolean shouldMatch, List<String> argList, OutStream out) throws Throwable { - List<String> copy = new ArrayList<String>(argList); - copy.add("-e"); - copy.add(enclosedQuery); + List<String> finalArgs = new ArrayList<String>(argList); + finalArgs.addAll(Arrays.asList("-e", enclosedQuery)); - String output = testCommandLineScript(copy, null, out); + String output = testCommandLineScript(finalArgs, out); boolean matches = output.contains(expectedPattern); if (shouldMatch != matches) { //failed @@ -484,7 +500,7 @@ public void testNullNonEmpty() throws Throwable { public void testGetVariableValue() throws Throwable { final String SCRIPT_TEXT = "set env:TERM;"; final String EXPECTED_PATTERN = "env:TERM"; - testScriptFile(SCRIPT_TEXT, getBaseArgs(miniHS2.getBaseJdbcURL()), OutStream.ERR, EXPECTED_PATTERN, true); + testScriptFile(SCRIPT_TEXT, getBaseArgs(miniHS2.getBaseJdbcURL()), OutStream.OUT, EXPECTED_PATTERN, true); } /** @@ -707,7 +723,7 @@ public void testNegativeScriptFile() throws Throwable { argList.add(scriptFile.getAbsolutePath()); try { - String output = testCommandLineScript(argList, null, OutStream.OUT); + String output = testCommandLineScript(argList, OutStream.OUT); if (output.contains(EXPECTED_PATTERN)) { fail("Output: " + output + " Negative pattern: " + EXPECTED_PATTERN); } @@ -971,6 +987,10 @@ public void testBeelineReconnect() throws Throwable { List<String> argList = getBaseArgs(miniHS2.getBaseJdbcURL()); final String SCRIPT_TEXT = "!close\n" + + // 3 line breaks mimic user input in the following sequence: + // 1. reconnect command + // 2. response to username prompt + // 3. response to password prompt "!reconnect\n\n\n" + "create table reconnecttest (d int);\nshow tables;\ndrop table reconnecttest;\n"; final String EXPECTED_PATTERN = "reconnecttest"; @@ -1027,10 +1047,7 @@ public void testSelectQueryWithNonEscapedSemiColon() throws Throwable { } /** - * Attempt to execute a simple script file with the usage of user & password variables in URL. - * Test for presence of an expected pattern - * in the output (stdout or stderr), fail if not found - * Print PASSED or FAILED + * Attempts to execute a simple script file and verifies that the database name appears in the prompt as expected. */ @Test public void testShowDbInPrompt() throws Throwable { diff --git a/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeelinePasswordOption.java b/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeelinePasswordOption.java index b6d01ce5789..0631c641177 100644 --- a/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeelinePasswordOption.java +++ b/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeelinePasswordOption.java @@ -17,6 +17,7 @@ */ package org.apache.hive.beeline; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; @@ -113,7 +114,7 @@ public void testPromptPasswordOptionWithOtherOptions() throws Exception { argList.add("-e"); argList.add("show tables;"); String output = connectBeelineWithUserPrompt(argList, "hivepassword"); - Assert.assertTrue("Table name " + tableName + " not found in the output", + Assert.assertTrue(String.format("Table name %s not found in the output: %s", tableName, output), output.contains(tableName.toLowerCase())); } @@ -131,7 +132,7 @@ public void testPromptPasswordOptionWithBeelineOpts() throws Exception { argList.add("-e"); argList.add("show tables;"); String output = connectBeelineWithUserPrompt(argList, "hivepassword"); - Assert.assertTrue("Table name " + tableName + " not found in the output", + Assert.assertTrue(String.format("Table name %s not found in the output: %s", tableName, output), output.contains(tableName.toLowerCase())); } @@ -149,8 +150,8 @@ public void testPromptPasswordVerifyBeelineOpts() throws Exception { argList.add("--maxColumnWidth=57"); argList.add("-e"); argList.add("show tables;"); - String output = connectWithPromptAndVerify(argList, "hivepassword", true, 57, null, null); - Assert.assertTrue("Table name " + tableName + " not found in the output", + String output = connectWithPromptAndVerify(argList, "hivepassword", 57, null, null); + Assert.assertTrue(String.format("Table name %s not found in the output: %s", tableName, output), output.contains(tableName.toLowerCase())); } @@ -169,9 +170,9 @@ public void testPromptPasswordWithHiveConf() throws Exception { argList.add("hive.cli.print.header=true"); argList.add("-e"); argList.add("show tables;"); - String output = connectWithPromptAndVerify(argList, "hivepassword", false, null, + String output = connectWithPromptAndVerify(argList, "hivepassword", -1, "hive.cli.print.header", "true"); - Assert.assertTrue("Table name " + tableName + " not found in the output", + Assert.assertTrue(String.format("Table name %s not found in the output: %s", tableName, output), output.contains(tableName.toLowerCase())); } @@ -188,7 +189,7 @@ public void testNoPasswordPrompt() throws Exception { argList.add("-e"); argList.add("show tables;"); String output = connectBeelineWithUserPrompt(argList); - Assert.assertTrue("Table name " + tableName + " not found in the output", + Assert.assertTrue(String.format("Table name %s not found in the output: %s", tableName, output), output.contains(tableName.toLowerCase())); } @@ -203,7 +204,7 @@ public void testNoPasswordPrompt2() throws Exception { argList.add("-e"); argList.add("show tables;"); String output = connectBeelineWithUserPrompt(argList); - Assert.assertTrue("Table name " + tableName + " not found in the output", + Assert.assertTrue(String.format("Table name %s not found in the output: %s", tableName, output), output.contains(tableName.toLowerCase())); } @@ -221,7 +222,7 @@ public void testPromptPassOptionLastWithBeelineOpts() throws Exception { argList.add("show tables;"); argList.add("-p"); String output = connectBeelineWithUserPrompt(argList, "hivepassword"); - Assert.assertTrue("Table name " + tableName + " not found in the output", + Assert.assertTrue(String.format("Table name %s not found in the output: %s", tableName, output), output.contains(tableName.toLowerCase())); } @@ -234,18 +235,18 @@ public void testPromptPassOptionLastWithBeelineOpts() throws Exception { * @param prompt - String value to be given to beeline prompt during connection * @param beelineOptName - Name of BeelineOpt to be verified * @param beelineOptValue - Expected value of value of BeeLineOpt + * @param expectedMaxColumnWidth - expected max column width to check (if not -1) * @param hiveConfKey - hive conf variable name to verify * @param expectedHiveConfValue - Expected value of hive conf variable * @return output of beeline from outputstream * @throws Exception */ private String connectWithPromptAndVerify(List<String> argList, String prompt, - boolean testMaxColumnWidthOption, Integer expectedMaxColumnWidth, String hiveConfKey, - String expectedHiveConfValue) throws Exception { + int expectedMaxColumnWidth, String hiveConfKey, String expectedHiveConfValue) throws Exception { BeeLine beeLine = null; InputStream inputStream = null; try { - beeLine = new BeeLine(); + beeLine = new BeeLineDummyTerminal(); ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream beelineOutputStream = new PrintStream(os); beeLine.setOutputStream(beelineOutputStream); @@ -254,12 +255,11 @@ private String connectWithPromptAndVerify(List<String> argList, String prompt, if (prompt != null) { inputStream = new ByteArrayInputStream(prompt.getBytes()); } - Assert.assertTrue(beeLine.begin(args, inputStream) == 0); - if (testMaxColumnWidthOption) { + int returnCode = beeLine.begin(args, inputStream); + assertEquals("Beeline.begin return code is not as expected", 0, returnCode); + if (expectedMaxColumnWidth != -1) { int maxColumnWidth = beeLine.getOpts().getMaxColumnWidth(); - Assert.assertTrue( - "Expected max columnWidth to be " + expectedMaxColumnWidth + " found " + maxColumnWidth, - maxColumnWidth == expectedMaxColumnWidth); + assertEquals("Unexpected max column width", expectedMaxColumnWidth, maxColumnWidth); } if (hiveConfKey != null) { String hiveConfValue = beeLine.getOpts().getHiveConfVariables().get(hiveConfKey); @@ -286,7 +286,7 @@ private String connectBeelineWithUserPrompt(List<String> argList) throws Excepti private String connectBeelineWithUserPrompt(List<String> argList, String prompt) throws Exception { - return connectWithPromptAndVerify(argList, prompt, false, null, null, null); + return connectWithPromptAndVerify(argList, prompt, -1, null, null); } /** diff --git a/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestHplSqlViaBeeLine.java b/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestHplSqlViaBeeLine.java index d9527cc78d3..994b0418023 100644 --- a/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestHplSqlViaBeeLine.java +++ b/itests/hive-unit/src/test/java/org/apache/hive/beeline/TestHplSqlViaBeeLine.java @@ -1418,10 +1418,10 @@ private void testScriptFile(String scriptText, List<String> argList, String expe try (PrintStream os = new PrintStream(new FileOutputStream(scriptFile))) { os.print(scriptText); } - List<String> copy = new ArrayList<>(argList); - copy.add("-f"); - copy.add(scriptFile.getAbsolutePath()); - String output = testCommandLineScript(copy, null, outStream); + List<String> finalArgs = new ArrayList<>(argList); + finalArgs.addAll(Arrays.asList("-f", scriptFile.getAbsolutePath())); + + String output = testCommandLineScript(finalArgs, outStream); if (scriptText.equals("SELECT UNIX_TIMESTAMP()")) { Pattern pattern = Pattern.compile("\\|\\s*(\\d+)\\s*\\|"); Matcher matcher = pattern.matcher(output); diff --git a/itests/hive-unit/src/test/java/org/apache/hive/beeline/hs2connection/BeelineWithHS2ConnectionFileTestBase.java b/itests/hive-unit/src/test/java/org/apache/hive/beeline/hs2connection/BeelineWithHS2ConnectionFileTestBase.java index 13679867c1b..bc20d733f95 100644 --- a/itests/hive-unit/src/test/java/org/apache/hive/beeline/hs2connection/BeelineWithHS2ConnectionFileTestBase.java +++ b/itests/hive-unit/src/test/java/org/apache/hive/beeline/hs2connection/BeelineWithHS2ConnectionFileTestBase.java @@ -35,7 +35,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.conf.HiveConf.ConfVars; -import org.apache.hive.beeline.BeeLine; +import org.apache.hive.beeline.BeeLineDummyTerminal; import org.apache.hive.jdbc.miniHS2.MiniHS2; import org.apache.hive.service.cli.CLIServiceClient; import org.apache.hive.service.cli.HiveSQLException; @@ -75,11 +75,11 @@ public abstract class BeelineWithHS2ConnectionFileTestBase { public String transportMode = null; - protected class TestBeeLine extends BeeLine { + protected class BeelineWithConfigFileManager extends BeeLineDummyTerminal { UserHS2ConnectionFileParser testHs2ConfigFileManager; ByteArrayOutputStream os; - TestBeeLine(List<String> defaultHS2ConnectionFiles) { + BeelineWithConfigFileManager(List<String> defaultHS2ConnectionFiles) { testHs2ConfigFileManager = new UserHS2ConnectionFileParser(defaultHS2ConnectionFiles); os = new ByteArrayOutputStream(); PrintStream beelineOutputStream = new PrintStream(os); @@ -87,7 +87,7 @@ protected class TestBeeLine extends BeeLine { setErrorStream(beelineOutputStream); } - TestBeeLine() { + BeelineWithConfigFileManager() { testHs2ConfigFileManager = new UserHS2ConnectionFileParser(null); os = new ByteArrayOutputStream(); PrintStream beelineOutputStream = new PrintStream(os); @@ -225,14 +225,14 @@ public BeelineResult(String output, int exitCode) { } protected BeelineResult getBeelineOutput(String path, String[] beelineArgs) throws Exception { - TestBeeLine beeLine = null; + BeelineWithConfigFileManager beeLine = null; try { if(path != null) { List<String> testLocations = new ArrayList<>(); testLocations.add(path); - beeLine = new TestBeeLine(testLocations); + beeLine = new BeelineWithConfigFileManager(testLocations); } else { - beeLine = new TestBeeLine(); + beeLine = new BeelineWithConfigFileManager(); } int exitCode = beeLine.begin(beelineArgs, null); String output = beeLine.getOutput(); diff --git a/itests/hive-unit/src/test/java/org/apache/hive/jdbc/miniHS2/TestHs2ConnectionMetricsBinary.java b/itests/hive-unit/src/test/java/org/apache/hive/jdbc/miniHS2/TestHs2ConnectionMetricsBinary.java index 6677b45a735..466d262dea3 100644 --- a/itests/hive-unit/src/test/java/org/apache/hive/jdbc/miniHS2/TestHs2ConnectionMetricsBinary.java +++ b/itests/hive-unit/src/test/java/org/apache/hive/jdbc/miniHS2/TestHs2ConnectionMetricsBinary.java @@ -25,6 +25,7 @@ import org.apache.hadoop.hive.common.metrics.metrics2.CodahaleMetrics; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hive.beeline.BeeLine; +import org.apache.hive.beeline.BeeLineDummyTerminal; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -78,7 +79,7 @@ public void testOpenConnectionMetrics() throws Exception { } private BeeLine openBeeLineConnection(String[] beelineArgs) throws IOException { - BeeLine beeLine = new BeeLine(); + BeeLine beeLine = new BeeLineDummyTerminal(); beeLine.begin(beelineArgs, null); return beeLine; } diff --git a/itests/hive-unit/src/test/java/org/apache/hive/service/server/InformationSchemaWithPrivilegeTestBase.java b/itests/hive-unit/src/test/java/org/apache/hive/service/server/InformationSchemaWithPrivilegeTestBase.java index f71aa4fa888..b43df43f8f0 100644 --- a/itests/hive-unit/src/test/java/org/apache/hive/service/server/InformationSchemaWithPrivilegeTestBase.java +++ b/itests/hive-unit/src/test/java/org/apache/hive/service/server/InformationSchemaWithPrivilegeTestBase.java @@ -52,6 +52,7 @@ import org.apache.hadoop.hive.ql.security.authorization.plugin.sqlstd.DummyHiveAuthorizationValidator; import org.apache.hadoop.hive.ql.security.authorization.plugin.sqlstd.SQLStdHiveAccessControllerWrapper; import org.apache.hive.beeline.BeeLine; +import org.apache.hive.beeline.BeeLineDummyTerminal; import org.apache.hive.jdbc.miniHS2.MiniHS2; import org.apache.hive.service.cli.CLIServiceClient; import org.apache.hive.service.cli.OperationHandle; @@ -298,7 +299,7 @@ public void test() throws Exception { List<String> args = new ArrayList<String>(baseArgs); args.add("-f"); args.add("../../standalone-metastore/metastore-server/src/main/sql/hive/hive-schema-" + hiveSchemaVer + ".hive.sql"); - BeeLine beeLine = new BeeLine(); + BeeLine beeLine = new BeeLineDummyTerminal(); int result = beeLine.begin(args.toArray(new String[] {}), null); beeLine.close(); Assert.assertEquals(result, 0); diff --git a/llap-server/src/java/org/apache/hadoop/hive/llap/cli/service/LlapServiceCommandLine.java b/llap-server/src/java/org/apache/hadoop/hive/llap/cli/service/LlapServiceCommandLine.java index ec3164d226c..5484e0ca1a1 100644 --- a/llap-server/src/java/org/apache/hadoop/hive/llap/cli/service/LlapServiceCommandLine.java +++ b/llap-server/src/java/org/apache/hadoop/hive/llap/cli/service/LlapServiceCommandLine.java @@ -21,8 +21,6 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; -import jline.TerminalFactory; - import java.util.Arrays; import java.util.Properties; import java.util.Set; @@ -36,6 +34,7 @@ import org.apache.commons.cli.ParseException; import org.apache.hadoop.hive.conf.HiveConf.ConfVars; import org.apache.hadoop.hive.llap.log.LogHelpers; +import org.jline.terminal.TerminalBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -403,7 +402,7 @@ private static void printUsage() { HelpFormatter hf = new HelpFormatter(); try { int width = hf.getWidth(); - int jlineWidth = TerminalFactory.get().getWidth(); + int jlineWidth = TerminalBuilder.terminal().getWidth(); width = Math.min(160, Math.max(jlineWidth, width)); hf.setWidth(width); } catch (Throwable t) { // Ignore diff --git a/llap-server/src/java/org/apache/hadoop/hive/llap/cli/status/LlapStatusServiceCommandLine.java b/llap-server/src/java/org/apache/hadoop/hive/llap/cli/status/LlapStatusServiceCommandLine.java index 61554b3b29f..1dbe3ae1d06 100644 --- a/llap-server/src/java/org/apache/hadoop/hive/llap/cli/status/LlapStatusServiceCommandLine.java +++ b/llap-server/src/java/org/apache/hadoop/hive/llap/cli/status/LlapStatusServiceCommandLine.java @@ -21,8 +21,6 @@ import java.util.Arrays; import java.util.Properties; -import jline.TerminalFactory; - import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; @@ -31,7 +29,7 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.hadoop.util.ExitUtil; - +import org.jline.terminal.TerminalBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -240,7 +238,7 @@ private static void printUsage() { HelpFormatter hf = new HelpFormatter(); try { int width = hf.getWidth(); - int jlineWidth = TerminalFactory.get().getWidth(); + int jlineWidth = TerminalBuilder.terminal().getWidth(); width = Math.min(160, Math.max(jlineWidth, width)); hf.setWidth(width); } catch (Throwable t) { // Ignore diff --git a/pom.xml b/pom.xml index bce696397f7..28a65a35a91 100644 --- a/pom.xml +++ b/pom.xml @@ -162,7 +162,8 @@ <jettison.version>1.5.4</jettison.version> <jetty.version>9.4.57.v20241219</jetty.version> <jersey.version>1.19.4</jersey.version> - <jline.version>2.14.6</jline.version> + <!-- HIVE-28992: only upgrade to newer than 3.25.0 if you tested the prompt --> + <jline.version>3.25.0</jline.version> <jms.version>2.0.2</jms.version> <joda.version>2.13.0</joda.version> <jodd.version>6.0.0</jodd.version> @@ -453,7 +454,7 @@ <version>${javolution.version}</version> </dependency> <dependency> - <groupId>jline</groupId> + <groupId>org.jline</groupId> <artifactId>jline</artifactId> <version>${jline.version}</version> </dependency> @@ -1421,6 +1422,11 @@ <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> + <dependency> + <groupId>org.fusesource.jansi</groupId> + <artifactId>jansi</artifactId> + <version>${jansi.version}</version> + </dependency> <!-- JDBC drivers --> <dependency> <groupId>com.microsoft.sqlserver</groupId> diff --git a/standalone-metastore/metastore-server/pom.xml b/standalone-metastore/metastore-server/pom.xml index eb329c44100..250273ceb11 100644 --- a/standalone-metastore/metastore-server/pom.xml +++ b/standalone-metastore/metastore-server/pom.xml @@ -303,7 +303,7 @@ <artifactId>sqlline</artifactId> </dependency> <dependency> - <groupId>jline</groupId> + <groupId>org.jline</groupId> <artifactId>jline</artifactId> </dependency> <dependency> diff --git a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/tools/metatool/HiveMetaToolCommandLine.java b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/tools/metatool/HiveMetaToolCommandLine.java index 58a8c921465..7df25c0c5e3 100644 --- a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/tools/metatool/HiveMetaToolCommandLine.java +++ b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/tools/metatool/HiveMetaToolCommandLine.java @@ -28,11 +28,10 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.hadoop.util.ExitUtil; - +import org.jline.terminal.TerminalBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jline.TerminalFactory; class HiveMetaToolCommandLine { private static final Logger LOGGER = LoggerFactory.getLogger(HiveMetaToolCommandLine.class.getName()); @@ -216,7 +215,7 @@ private static void printUsage() { HelpFormatter hf = new HelpFormatter(); try { int width = hf.getWidth(); - int jlineWidth = TerminalFactory.get().getWidth(); + int jlineWidth = TerminalBuilder.terminal().getWidth(); width = Math.min(160, Math.max(jlineWidth, width)); hf.setWidth(width); } catch (Throwable t) { // Ignore diff --git a/standalone-metastore/pom.xml b/standalone-metastore/pom.xml index 14baad61338..6857f53bab7 100644 --- a/standalone-metastore/pom.xml +++ b/standalone-metastore/pom.xml @@ -101,7 +101,8 @@ <protoc.path>${env.PROTOC_PATH}</protoc.path> <io.grpc.version>1.72.0</io.grpc.version> <sqlline.version>1.9.0</sqlline.version> - <jline.version>2.14.6</jline.version> + <!-- HIVE-28992: only upgrade to newer than 3.25.0 if you tested the prompt --> + <jline.version>3.25.0</jline.version> <ST4.version>4.0.4</ST4.version> <storage-api.version>4.2.0-SNAPSHOT</storage-api.version> <beanutils.version>1.9.4</beanutils.version> @@ -338,9 +339,15 @@ <groupId>sqlline</groupId> <artifactId>sqlline</artifactId> <version>${sqlline.version}</version> + <exclusions> + <exclusion> + <groupId>org.jline</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> - <groupId>jline</groupId> + <groupId>org.jline</groupId> <artifactId>jline</artifactId> <version>${jline.version}</version> </dependency>