Repository: incubator-impala
Updated Branches:
  refs/heads/master ed87c4060 -> 679ebc1ac


IMPALA-992: Rerun past queries from history in shell

This patch adds a new command "rerun" and a shortcut "@" to impala-shell
. Users can rerun a certain query by its index given by history command.
A valid index is an integer in [1, history_length] or
[-history_length, -1]. Negative values index history in reverse order.
For example, "@1;" or "rerun 1;" reruns the first query shown in history
and "@-1;" reruns the last query. The rerun command itself won't appear
in history. The history index is 1-based and increasing. Old entries
might be truncated when impala-shell starts, and the indexes will be
realigned to 1, so the same index may refer to different commands among
multiple impala-shell instances.

Testing: A test case test_rerun is added to
shell/test_shell_interactive.py

Change-Id: Ifc28e8ce07845343267224c3b9ccb71b29a524d2
Reviewed-on: http://gerrit.cloudera.org:8080/7674
Reviewed-by: Sailesh Mukil <[email protected]>
Tested-by: Impala Public Jenkins


Project: http://git-wip-us.apache.org/repos/asf/incubator-impala/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-impala/commit/c871e007
Tree: http://git-wip-us.apache.org/repos/asf/incubator-impala/tree/c871e007
Diff: http://git-wip-us.apache.org/repos/asf/incubator-impala/diff/c871e007

Branch: refs/heads/master
Commit: c871e007bed662732944fd5bb8d88e306ff4865b
Parents: ed87c40
Author: Tianyi Wang <[email protected]>
Authored: Mon Aug 14 19:03:09 2017 -0700
Committer: Impala Public Jenkins <[email protected]>
Committed: Wed Aug 23 03:34:45 2017 +0000

----------------------------------------------------------------------
 shell/impala_shell.py                 | 49 +++++++++++++++++++++++++++---
 tests/shell/test_shell_interactive.py | 36 ++++++++++++++++++++++
 2 files changed, 81 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/c871e007/shell/impala_shell.py
----------------------------------------------------------------------
diff --git a/shell/impala_shell.py b/shell/impala_shell.py
index c45f932..6d9df4f 100755
--- a/shell/impala_shell.py
+++ b/shell/impala_shell.py
@@ -46,6 +46,8 @@ from thrift.Thrift import TException
 
 VERSION_FORMAT = "Impala Shell v%(version)s (%(git_hash)s) built on 
%(build_date)s"
 VERSION_STRING = "build version not available"
+READLINE_UNAVAILABLE_ERROR = "The readline module was either not found or 
disabled. " \
+                             "Command history will not be collected."
 
 # Tarball / packaging build makes impala_build_version available
 try:
@@ -76,7 +78,7 @@ class ImpalaPrettyTable(prettytable.PrettyTable):
       value = unicode(value, self.encoding, "replace")
     return value
 
-class ImpalaShell(cmd.Cmd):
+class ImpalaShell(object, cmd.Cmd):
   """ Simple Impala Shell.
 
   Basic usage: type connect <host:port> to connect to an impalad
@@ -1068,15 +1070,40 @@ class ImpalaShell(cmd.Cmd):
 
   def do_history(self, args):
     """Display command history"""
-    # Deal with readline peculiarity. When history does not exists,
+    # Deal with readline peculiarity. When history does not exist,
     # readline returns 1 as the history length and stores 'None' at index 0.
     if self.readline and self.readline.get_current_history_length() > 0:
       for index in xrange(1, self.readline.get_current_history_length() + 1):
         cmd = self.readline.get_history_item(index)
         print_to_stderr('[%d]: %s' % (index, cmd))
     else:
-      print_to_stderr("The readline module was either not found or disabled. 
Command "
-                      "history will not be collected.")
+      print_to_stderr(READLINE_UNAVAILABLE_ERROR)
+
+  def do_rerun(self, args):
+    """Rerun a command with an command index in history
+    Example: @1;
+    """
+    history_len = self.readline.get_current_history_length()
+    # Rerun command shouldn't appear in history
+    self.readline.remove_history_item(history_len - 1)
+    history_len -= 1
+    if not self.readline:
+      print_to_stderr(READLINE_UNAVAILABLE_ERROR)
+      return CmdStatus.ERROR
+    try:
+      cmd_idx = int(args)
+    except ValueError:
+      print_to_stderr("Command index to be rerun must be an integer.")
+      return CmdStatus.ERROR
+    if not (0 < cmd_idx <= history_len or -history_len <= cmd_idx < 0):
+      print_to_stderr("Command index out of range. Valid range: [1, {0}] and 
[-{0}, -1]"
+                      .format(history_len))
+      return CmdStatus.ERROR
+    if cmd_idx < 0:
+      cmd_idx += history_len + 1
+    cmd = self.readline.get_history_item(cmd_idx)
+    print_to_stderr("Rerunning " + cmd)
+    return self.onecmd(cmd.rstrip(";"))
 
   def do_tip(self, args):
     """Print a random tip"""
@@ -1124,6 +1151,20 @@ class ImpalaShell(cmd.Cmd):
         # The history file is not writable, disable readline.
         self._disable_readline()
 
+  def parseline(self, line):
+    """Parse the line into a command name and a string containing
+    the arguments.  Returns a tuple containing (command, args, line).
+    'command' and 'args' may be None if the line couldn't be parsed.
+    'line' in return tuple is the rewritten original line, with leading
+    and trailing space removed and special characters transformed into
+    their aliases.
+    """
+    line = line.strip()
+    if line and line[0] == '@':
+      line = 'rerun ' + line[1:]
+    return super(ImpalaShell, self).parseline(line)
+
+
   def _replace_history_delimiters(self, src_delim, tgt_delim):
     """Replaces source_delim with target_delim for all items in history.
 

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/c871e007/tests/shell/test_shell_interactive.py
----------------------------------------------------------------------
diff --git a/tests/shell/test_shell_interactive.py 
b/tests/shell/test_shell_interactive.py
index 9222637..7b85a55 100755
--- a/tests/shell/test_shell_interactive.py
+++ b/tests/shell/test_shell_interactive.py
@@ -232,6 +232,42 @@ class TestImpalaShellInteractive(object):
       assert query in result.stderr, "'%s' not in '%s'" % (query, 
result.stderr)
 
   @pytest.mark.execute_serially
+  def test_rerun(self):
+    """Smoke test for the 'rerun' command"""
+    # Clear history first.
+    if os.path.exists(SHELL_HISTORY_FILE):
+      os.remove(SHELL_HISTORY_FILE)
+    assert not os.path.exists(SHELL_HISTORY_FILE)
+    child_proc = pexpect.spawn(SHELL_CMD)
+    child_proc.expect(":21000] >")
+    self._expect_with_cmd(child_proc, "@1", ("Command index out of range"))
+    self._expect_with_cmd(child_proc, "rerun -1", ("Command index out of 
range"))
+    self._expect_with_cmd(child_proc, "select 'first_command'", 
("first_command"))
+    self._expect_with_cmd(child_proc, "rerun 1", ("first_command"))
+    self._expect_with_cmd(child_proc, "@ -1", ("first_command"))
+    self._expect_with_cmd(child_proc, "select 'second_command'", 
("second_command"))
+    child_proc.sendline('history;')
+    child_proc.expect(":21000] >")
+    assert '[1]: select \'first_command\';' in child_proc.before;
+    assert '[2]: select \'second_command\';' in child_proc.before;
+    assert '[3]: history;' in child_proc.before;
+    # Rerunning command should not add an entry into history.
+    assert '[4]' not in child_proc.before;
+    self._expect_with_cmd(child_proc, "@0", ("Command index out of range"))
+    self._expect_with_cmd(child_proc, "rerun   4", ("Command index out of 
range"))
+    self._expect_with_cmd(child_proc, "@-4", ("Command index out of range"))
+    self._expect_with_cmd(child_proc, " @ 3 ", ("second_command"))
+    self._expect_with_cmd(child_proc, "@-3", ("first_command"))
+    self._expect_with_cmd(child_proc, "@",
+                          ("Command index to be rerun must be an integer."))
+    self._expect_with_cmd(child_proc, "@1foo",
+                          ("Command index to be rerun must be an integer."))
+    self._expect_with_cmd(child_proc, "@1 2",
+                          ("Command index to be rerun must be an integer."))
+    self._expect_with_cmd(child_proc, "rerun1", ("Syntax error"))
+    child_proc.sendline('quit;')
+
+  @pytest.mark.execute_serially
   def test_tip(self):
     """Smoke test for the TIP command"""
     # Temporarily add impala_shell module to path to get at TIPS list for 
verification

Reply via email to