(cqlsh): Support for query paging patch by Mikhail Stepura; reviewed by Aleksey Yeschenko for CASSANDRA-7514
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/f1f5f5fd Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/f1f5f5fd Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/f1f5f5fd Branch: refs/heads/trunk Commit: f1f5f5fd3ac9bcf20fd68bae2f639ab12e2c360d Parents: a44d641 Author: Mikhail Stepura <mish...@apache.org> Authored: Fri Aug 29 13:13:16 2014 -0700 Committer: Mikhail Stepura <mish...@apache.org> Committed: Sun Sep 7 20:09:14 2014 -0700 ---------------------------------------------------------------------- CHANGES.txt | 1 + bin/cqlsh | 165 +++++++++++++--------- pylib/cqlshlib/test/test_cqlsh_completion.py | 2 +- 3 files changed, 97 insertions(+), 71 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1f5f5fd/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 18d6872..7737269 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.1.1 + * (cqlsh): Support for query paging (CASSANDRA-7514) * (cqlsh): Show progress of COPY operations (CASSANDRA-7789) * Add syntax to remove multiple elements from a map (CASSANDRA-6599) * Support non-equals conditions in lightweight transactions (CASSANDRA-6839) http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1f5f5fd/bin/cqlsh ---------------------------------------------------------------------- diff --git a/bin/cqlsh b/bin/cqlsh index dfce885..d61a008 100755 --- a/bin/cqlsh +++ b/bin/cqlsh @@ -105,7 +105,7 @@ except ImportError, e: 'Module load path: %r\n\n' 'Error: %s\n' % (sys.executable, sys.path, e)) -from cassandra.cluster import Cluster +from cassandra.cluster import Cluster, PagedResult from cassandra.query import SimpleStatement, ordered_dict_factory from cassandra.policies import WhiteListRoundRobinPolicy from cassandra.metadata import protect_name, protect_names, protect_value @@ -147,7 +147,6 @@ DEFAULT_PROTOCOL_VERSION = 3 DEFAULT_TIME_FORMAT = '%Y-%m-%d %H:%M:%S%z' DEFAULT_FLOAT_PRECISION = 5 -DEFAULT_SELECT_LIMIT = 10000 DEFAULT_MAX_TRACE_WAIT = 10 if readline is not None and readline.__doc__ is not None and 'libedit' in readline.__doc__: @@ -204,6 +203,7 @@ my_commands_ending_with_newline = ( 'debug', 'tracing', 'expand', + 'paging', 'exit', 'quit' ) @@ -231,6 +231,7 @@ cqlsh_extra_syntax_rules = r''' | <tracingCommand> | <expandCommand> | <exitCommand> + | <pagingCommand> ; <describeCommand> ::= ( "DESCRIBE" | "DESC" ) @@ -294,6 +295,9 @@ cqlsh_extra_syntax_rules = r''' <expandCommand> ::= "EXPAND" ( switch=( "ON" | "OFF" ) )? ; +<pagingCommand> ::= "PAGING" ( switch=( "ON" | "OFF" ) )? + ; + <exitCommand> ::= "exit" | "quit" ; @@ -481,8 +485,10 @@ class Shell(cmd.Cmd): stop = False last_hist = None shunted_query_out = None + use_paging = True csv_dialect_defaults = dict(delimiter=',', doublequote=False, escapechar='\\', quotechar='"') + default_page_size = 100 def __init__(self, hostname, port, color=False, username=None, password=None, encoding=None, stdin=None, tty=True, @@ -899,16 +905,12 @@ class Shell(cmd.Cmd): stop_tracing = ksname == 'system_traces' or (ksname is None and self.current_keyspace == 'system_traces') self.tracing_enabled = self.tracing_enabled and not stop_tracing statement = parsed.extract_orig() - with_default_limit = parsed.get_binding('limit') is None - if with_default_limit: - statement = "%s LIMIT %d;" % (statement[:-1], DEFAULT_SELECT_LIMIT) - self.perform_statement(statement, with_default_limit=with_default_limit) + self.perform_statement(statement) self.tracing_enabled = tracing_was_enabled - def perform_statement(self, statement, with_default_limit=False): - stmt = SimpleStatement(statement, consistency_level=self.consistency_level) - result = self.perform_simple_statement(stmt, - with_default_limit=with_default_limit) + def perform_statement(self, statement): + stmt = SimpleStatement(statement, consistency_level=self.consistency_level, fetch_size=self.default_page_size if self.use_paging else None) + result = self.perform_simple_statement(stmt) if self.tracing_enabled: if stmt.trace: print_trace(self, stmt.trace) @@ -924,7 +926,7 @@ class Shell(cmd.Cmd): cf = self.cql_unprotect_name(parsed.get_binding('cfname')) return self.get_table_meta(ks, cf) - def perform_simple_statement(self, statement, with_default_limit=False): + def perform_simple_statement(self, statement): if not statement: return False rows = None @@ -941,7 +943,7 @@ class Shell(cmd.Cmd): return False if statement.query_string[:6].lower() == 'select' or statement.query_string.lower().startswith("list"): - self.print_result(rows, with_default_limit, self.parse_for_table_meta(statement.query_string)) + self.print_result(rows, self.parse_for_table_meta(statement.query_string)) elif rows: # CAS INSERT/UPDATE self.writeresult("") @@ -949,14 +951,33 @@ class Shell(cmd.Cmd): self.flush_output() return True - def print_result(self, rows, with_default_limit, table_meta): + def print_result(self, rows, table_meta): self.decoding_errors = [] self.writeresult("") - if rows: - rows = list(rows) # this may be an iterator if the result is large enough to page - self.print_static_result(rows, table_meta) - self.writeresult("(%d rows)" % len(rows or [])) + if isinstance(rows, PagedResult) and self.tty: + num_rows = 0 + while True: + page = list(rows.current_response) + if not page: + break + num_rows += len(page) + self.print_static_result(page, table_meta) + if not rows.response_future.has_more_pages: + break + raw_input("---MORE---") + + rows.response_future.start_fetching_next_page() + result = rows.response_future.result() + if rows.response_future.has_more_pages: + rows.current_response = result.current_response + else: + rows.current_response = iter(result) + else: + rows = list(rows or []) + num_rows = len(rows) + self.print_static_result(rows, table_meta) + self.writeresult("(%d rows)" % num_rows) if self.decoding_errors: for err in self.decoding_errors[:2]: @@ -965,13 +986,6 @@ class Shell(cmd.Cmd): self.writeresult('%d more decoding errors suppressed.' % (len(self.decoding_errors) - 2), color=RED) - if with_default_limit: - if len(rows) == DEFAULT_SELECT_LIMIT: - self.writeresult("Default LIMIT of %d was used. " - "Specify your own LIMIT clause to get more results." - % DEFAULT_SELECT_LIMIT, color=RED) - self.writeresult("") - def print_static_result(self, rows, table_meta): if not rows: # print header only @@ -1613,29 +1627,7 @@ class Shell(cmd.Cmd): TRACING with no arguments shows the current tracing status. """ - switch = parsed.get_binding('switch') - if switch is None: - if self.tracing_enabled: - print "Tracing is currently enabled. Use TRACING OFF to disable" - else: - print "Tracing is currently disabled. Use TRACING ON to enable." - return - - if switch.upper() == 'ON': - if self.tracing_enabled: - self.printerr('Tracing is already enabled. ' - 'Use TRACING OFF to disable.') - return - self.tracing_enabled = True - print 'Now tracing requests.' - return - - if switch.upper() == 'OFF': - if not self.tracing_enabled: - self.printerr('Tracing is not enabled.') - return - self.tracing_enabled = False - print 'Disabled tracing.' + self.tracing_enabled = SwitchCommand("TRACING", "Tracing").execute(self.tracing_enabled, parsed, self.printerr) def do_expand(self, parsed): """ @@ -1655,29 +1647,7 @@ class Shell(cmd.Cmd): EXPAND with no arguments shows the current value of expand setting. """ - switch = parsed.get_binding('switch') - if switch is None: - if self.expand_enabled: - print "Expanded output is currently enabled. Use EXPAND OFF to disable" - else: - print "Expanded output is currently disabled. Use EXPAND ON to enable." - return - - if switch.upper() == 'ON': - if self.expand_enabled: - self.printerr('Expanded output is already enabled. ' - 'Use EXPAND OFF to disable.') - return - self.expand_enabled = True - print 'Now printing expanded output' - return - - if switch.upper() == 'OFF': - if not self.expand_enabled: - self.printerr('Expanded output is not enabled.') - return - self.expand_enabled = False - print 'Disabled expanded output.' + self.expand_enabled = SwitchCommand("EXPAND", "Expanded output").execute(self.expand_enabled, parsed, self.printerr) def do_consistency(self, parsed): """ @@ -1753,6 +1723,26 @@ class Shell(cmd.Cmd): else: self.printerr("*** No help on %s" % (t,)) + def do_paging(self, parsed): + """ + PAGING [cqlsh] + + Enables or disables query paging. + + PAGING ON + + Enables query paging for all further queries. + + PAGING OFF + + Disables paging. + + PAGING + + PAGING with no arguments shows the current query paging status. + """ + self.use_paging = SwitchCommand("PAGING", "Query paging").execute(self.use_paging, parsed, self.printerr) + def applycolor(self, text, color=None): if not color or not self.color: return text @@ -1775,6 +1765,41 @@ class Shell(cmd.Cmd): self.writeresult(text, color, newline=newline, out=sys.stderr) +class SwitchCommand(object): + command = None + description = None + + def __init__(self, command, desc): + self.command = command + self.description = desc + + def execute(self, state, parsed, printerr): + switch = parsed.get_binding('switch') + if switch is None: + if state: + print "%s is currently enabled. Use %s OFF to disable" \ + % (self.description, self.command) + else: + print "%s is currently disabled. Use %s ON to enable." \ + % (self.description, self.command) + return state + + if switch.upper() == 'ON': + if state: + printerr('%s is already enabled. Use %s OFF to disable.' + % (self.description, self.command)) + return state + print 'Now %s is enabled' % (self.description,) + return True + + if switch.upper() == 'OFF': + if not state: + printerr('%s is not enabled.' % (self.description,)) + return state + print 'Disabled %s.' % (self.description,) + return False + + def option_with_default(cparser_getter, section, option, default=None): try: return cparser_getter(section, option) http://git-wip-us.apache.org/repos/asf/cassandra/blob/f1f5f5fd/pylib/cqlshlib/test/test_cqlsh_completion.py ---------------------------------------------------------------------- diff --git a/pylib/cqlshlib/test/test_cqlsh_completion.py b/pylib/cqlshlib/test/test_cqlsh_completion.py index 8d3b7c4..820414d 100644 --- a/pylib/cqlshlib/test/test_cqlsh_completion.py +++ b/pylib/cqlshlib/test/test_cqlsh_completion.py @@ -98,7 +98,7 @@ class TestCqlshCompletion(CqlshCompletionCase): def test_complete_on_empty_string(self): self.trycompletions('', choices=('?', 'ALTER', 'BEGIN', 'CAPTURE', 'CONSISTENCY', 'COPY', 'CREATE', 'DEBUG', 'DELETE', 'DESC', 'DESCRIBE', - 'DROP', 'GRANT', 'HELP', 'INSERT', 'LIST', 'REVOKE', + 'DROP', 'GRANT', 'HELP', 'INSERT', 'LIST', 'PAGING', 'REVOKE', 'SELECT', 'SHOW', 'SOURCE', 'TRACING', 'EXPAND', 'TRUNCATE', 'UPDATE', 'USE', 'exit', 'quit'))