diff --git a/bin/cqlsh b/bin/cqlsh index 2078107c21..9dffa7f790 100755 --- a/bin/cqlsh +++ b/bin/cqlsh @@ -36,7 +36,7 @@ description = "CQL Shell for Apache Cassandra" version = "5.0.1" from StringIO import StringIO -from contextlib import contextmanager, closing +from contextlib import contextmanager from glob import glob import cmd @@ -79,6 +79,7 @@ if myplatform == 'Linux': if os.environ.get('CQLSH_NO_BUNDLED', ''): ZIPLIB_DIRS = () + def find_zip(libprefix): for ziplibdir in ZIPLIB_DIRS: zips = glob(os.path.join(ziplibdir, libprefix + '*.zip')) @@ -111,7 +112,6 @@ from cassandra.cluster import Cluster, PagedResult from cassandra.query import SimpleStatement, ordered_dict_factory from cassandra.policies import WhiteListRoundRobinPolicy from cassandra.protocol import QueryMessage, ResultMessage -from cassandra.marshal import int16_pack, int32_pack, uint64_pack from cassandra.metadata import protect_name, protect_names, protect_value, KeyspaceMetadata, TableMetadata, ColumnMetadata from cassandra.auth import PlainTextAuthProvider @@ -145,6 +145,9 @@ if readline is not None and readline.__doc__ is not None and 'libedit' in readli else: DEFAULT_COMPLETEKEY = 'tab' +cqldocs = None +cqlruleset = None + epilog = """Connects to %(DEFAULT_HOST)s:%(DEFAULT_PORT)d by default. These defaults can be changed by setting $CQLSH_HOST and/or $CQLSH_PORT. When a host (and optional port number) are given on the command line, they take @@ -227,13 +230,17 @@ my_commands_ending_with_newline = ( 'quit' ) + cqlsh_syntax_completers = [] + + def cqlsh_syntax_completer(rulename, termname): def registrator(f): cqlsh_syntax_completers.append((rulename, termname, f)) return f return registrator + cqlsh_extra_syntax_rules = r''' ::= | ( ";" | "\n" ) @@ -325,13 +332,15 @@ cqlsh_extra_syntax_rules = r''' ::= "?" ; ''' + @cqlsh_syntax_completer('helpCommand', 'topic') def complete_help(ctxt, cqlsh): - return sorted([ t.upper() for t in cqldocs.get_help_topics() + cqlsh.get_help_topics() ]) + return sorted([t.upper() for t in cqldocs.get_help_topics() + cqlsh.get_help_topics()]) + def complete_source_quoted_filename(ctxt, cqlsh): - partial = ctxt.get_binding('partial', '') - head, tail = os.path.split(partial) + partial_path = ctxt.get_binding('partial', '') + head, tail = os.path.split(partial_path) exhead = os.path.expanduser(head) try: contents = os.listdir(exhead or '.') @@ -346,21 +355,22 @@ def complete_source_quoted_filename(ctxt, cqlsh): annotated.append(match) return annotated -cqlsh_syntax_completer('sourceCommand', 'fname') \ - (complete_source_quoted_filename) -cqlsh_syntax_completer('captureCommand', 'fname') \ - (complete_source_quoted_filename) + +cqlsh_syntax_completer('sourceCommand', 'fname')(complete_source_quoted_filename) +cqlsh_syntax_completer('captureCommand', 'fname')(complete_source_quoted_filename) + @cqlsh_syntax_completer('copyCommand', 'fname') def copy_fname_completer(ctxt, cqlsh): lasttype = ctxt.get_binding('*LASTTYPE*') if lasttype == 'unclosedString': return complete_source_quoted_filename(ctxt, cqlsh) - partial = ctxt.get_binding('partial') - if partial == '': + partial_path = ctxt.get_binding('partial') + if partial_path == '': return ["'"] return () + @cqlsh_syntax_completer('copyCommand', 'colnames') def complete_copy_column_names(ctxt, cqlsh): existcols = map(cqlsh.cql_unprotect_name, ctxt.get_binding('colnames', ())) @@ -371,8 +381,10 @@ def complete_copy_column_names(ctxt, cqlsh): return [colnames[0]] return set(colnames[1:]) - set(existcols) + COPY_OPTIONS = ('DELIMITER', 'QUOTE', 'ESCAPE', 'HEADER', 'ENCODING', 'NULL') + @cqlsh_syntax_completer('copyOption', 'optnames') def complete_copy_options(ctxt, cqlsh): optnames = map(str.upper, ctxt.get_binding('optnames', ())) @@ -382,6 +394,7 @@ def complete_copy_options(ctxt, cqlsh): opts -= ('ENCODING',) return opts + @cqlsh_syntax_completer('copyOption', 'optvals') def complete_copy_opt_values(ctxt, cqlsh): optnames = ctxt.get_binding('optnames', ()) @@ -390,21 +403,27 @@ def complete_copy_opt_values(ctxt, cqlsh): return ['true', 'false'] return [cqlhandling.Hint('')] + class NoKeyspaceError(Exception): pass + class KeyspaceNotFound(Exception): pass + class ColumnFamilyNotFound(Exception): pass + class VersionNotSupported(Exception): pass + class UserTypeNotFound(Exception): pass + class DecodeError(Exception): verb = 'decode' @@ -426,9 +445,11 @@ class DecodeError(Exception): def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self.message()) + class FormatError(DecodeError): verb = 'format' + def full_cql_version(ver): while ver.count('.') < 2: ver += '.0' @@ -436,6 +457,7 @@ def full_cql_version(ver): vertuple = tuple(map(int, ver_parts[0].split('.')) + [ver_parts[1]]) return ver, vertuple + def format_value(val, output_encoding, addcolor=False, date_time_format=None, float_precision=None, colormap=None, nullval=None): if isinstance(val, DecodeError): @@ -447,6 +469,7 @@ def format_value(val, output_encoding, addcolor=False, date_time_format=None, addcolor=addcolor, nullval=nullval, date_time_format=date_time_format, float_precision=float_precision) + def show_warning_without_quoting_line(message, category, filename, lineno, file=None, line=None): if file is None: file = sys.stderr @@ -457,6 +480,7 @@ def show_warning_without_quoting_line(message, category, filename, lineno, file= warnings.showwarning = show_warning_without_quoting_line warnings.filterwarnings('always', category=cql3handling.UnexpectedTableStructure) + def describe_interval(seconds): desc = [] for length, unit in ((86400, 'day'), (3600, 'hour'), (60, 'minute')): @@ -477,6 +501,7 @@ def describe_interval(seconds): def auto_format_udts(): # when we see a new user defined type, set up the shell formatting for it udt_apply_params = cassandra.cqltypes.UserType.apply_parameters + def new_apply_params(cls, *args, **kwargs): udt_class = udt_apply_params(*args, **kwargs) formatter_for(udt_class.typename)(format_value_utype) @@ -485,6 +510,7 @@ def auto_format_udts(): cassandra.cqltypes.UserType.udt_apply_parameters = classmethod(new_apply_params) make_udt_class = cassandra.cqltypes.UserType.make_udt_class + def new_make_udt_class(cls, *args, **kwargs): udt_class = make_udt_class(*args, **kwargs) formatter_for(udt_class.tuple_type.__name__)(format_value_utype) @@ -665,9 +691,9 @@ class Shell(cmd.Cmd): # check column role and color appropriately if table_meta: if name in [col.name for col in table_meta.partition_key]: - column_colors.default_factory = lambda : RED + column_colors.default_factory = lambda: RED elif name in [col.name for col in table_meta.clustering_key]: - column_colors.default_factory = lambda : CYAN + column_colors.default_factory = lambda: CYAN return self.myformat_value(name, colormap=column_colors) def report_connection(self): @@ -769,7 +795,7 @@ class Shell(cmd.Cmd): ksmeta = self.get_keyspace_meta(ksname) if tablename not in ksmeta.tables: - if ksname == 'system_auth' and tablename in ['roles','role_permissions']: + if ksname == 'system_auth' and tablename in ['roles', 'role_permissions']: self.get_fake_auth_table_meta(ksname, tablename) else: raise ColumnFamilyNotFound("Column family %r not found" % tablename) @@ -793,7 +819,7 @@ class Shell(cmd.Cmd): table_meta.columns['resource'] = ColumnMetadata(table_meta, 'resource', cassandra.cqltypes.UTF8Type) table_meta.columns['permission'] = ColumnMetadata(table_meta, 'permission', cassandra.cqltypes.UTF8Type) else: - raise ColumnFamilyNotFoundException("Column family %r not found" % tablename) + raise ColumnFamilyNotFound("Column family %r not found" % tablename) def get_usertypes_meta(self): data = self.session.execute("select * from system.schema_usertypes") @@ -813,7 +839,7 @@ class Shell(cmd.Cmd): def reset_statement(self): self.reset_prompt() self.statement.truncate(0) - self.empty_lines = 0; + self.empty_lines = 0 def reset_prompt(self): if self.current_keyspace is None: @@ -822,7 +848,7 @@ class Shell(cmd.Cmd): self.set_prompt(self.keyspace_prompt % self.current_keyspace, True) def set_continue_prompt(self): - if self.empty_lines >=3: + if self.empty_lines >= 3: self.set_prompt("Statements are terminated with a ';'. You can press CTRL-C to cancel an incomplete statement.") self.empty_lines = 0 return @@ -881,7 +907,6 @@ class Shell(cmd.Cmd): return yield newline - def cmdloop(self): """ Adapted from cmd.Cmd's version, because there is literally no way with @@ -1023,7 +1048,7 @@ class Shell(cmd.Cmd): parsed = cqlruleset.cql_parse(query_string)[1] except IndexError: return None - ks = self.cql_unprotect_name(parsed.get_binding('ksname', None)) + ks = self.cql_unprotect_name(parsed.get_binding('ksname', None)) cf = self.cql_unprotect_name(parsed.get_binding('cfname')) return self.get_table_meta(ks, cf) @@ -1046,9 +1071,9 @@ class Shell(cmd.Cmd): if statement.query_string[:6].lower() == 'select': self.print_result(rows, self.parse_for_table_meta(statement.query_string)) elif statement.query_string.lower().startswith("list users") or statement.query_string.lower().startswith("list roles"): - self.print_result(rows, self.get_table_meta('system_auth','roles')) + self.print_result(rows, self.get_table_meta('system_auth', 'roles')) elif statement.query_string.lower().startswith("list"): - self.print_result(rows, self.get_table_meta('system_auth','role_permissions')) + self.print_result(rows, self.get_table_meta('system_auth', 'role_permissions')) elif rows: # CAS INSERT/UPDATE self.writeresult("") @@ -1126,7 +1151,7 @@ class Shell(cmd.Cmd): # stop if there are no rows if formatted_values is None: self.writeresult("") - return; + return # print row data for row in formatted_values: @@ -1178,7 +1203,6 @@ class Shell(cmd.Cmd): prevlines = self.statement.getvalue() wholestmt = prevlines + curline begidx = readline.get_begidx() + len(prevlines) - endidx = readline.get_endidx() + len(prevlines) stuff_to_complete = wholestmt[:begidx] return cqlruleset.cql_complete(stuff_to_complete, text, cassandra_conn=self, debug=debug_completion, startsymbol='cqlshCommand') @@ -1412,7 +1436,7 @@ class Shell(cmd.Cmd): fname = os.path.expanduser(self.cql_unprotect_value(fname)) copyoptnames = map(str.lower, parsed.get_binding('optnames', ())) copyoptvals = map(self.cql_unprotect_value, parsed.get_binding('optvals', ())) - cleancopyoptvals = [optval.decode('string-escape') for optval in copyoptvals] + cleancopyoptvals = [optval.decode('string-escape') for optval in copyoptvals] opts = dict(zip(copyoptnames, cleancopyoptvals)) timestart = time.time() @@ -1464,7 +1488,6 @@ class Shell(cmd.Cmd): try: if header: linesource.next() - table_meta = self.get_table_meta(ks, cf) reader = csv.reader(linesource, **dialect_options) from multiprocessing import Process, Pipe, cpu_count @@ -1980,7 +2003,7 @@ class Shell(cmd.Cmd): pdb.set_trace() def get_help_topics(self): - topics = [ t[3:] for t in dir(self) if t.startswith('do_') and getattr(self, t, None).__doc__] + topics = [t[3:] for t in dir(self) if t.startswith('do_') and getattr(self, t, None).__doc__] for hide_from_help in ('quit',): topics.remove(hide_from_help) return topics @@ -1998,9 +2021,9 @@ class Shell(cmd.Cmd): """ topics = parsed.get_binding('topic', ()) if not topics: - shell_topics = [ t.upper() for t in self.get_help_topics() ] + shell_topics = [t.upper() for t in self.get_help_topics()] self.print_topics("\nDocumented shell commands:", shell_topics, 15, 80) - cql_topics = [ t.upper() for t in cqldocs.get_help_topics() ] + cql_topics = [t.upper() for t in cqldocs.get_help_topics()] self.print_topics("CQL help topics:", cql_topics, 15, 80) return for t in topics: @@ -2095,6 +2118,7 @@ def option_with_default(cparser_getter, section, option, default=None): except ConfigParser.Error: return default + def raw_option_with_default(configs, section, option, default=None): """ Same (almost) as option_with_default() but won't do any string interpolation. @@ -2105,6 +2129,7 @@ def raw_option_with_default(configs, section, option, default=None): except ConfigParser.Error: return default + def should_use_color(): if not sys.stdout.isatty(): return False @@ -2122,6 +2147,7 @@ def should_use_color(): pass return True + def read_options(cmdlineargs, environment): configs = ConfigParser.SafeConfigParser() configs.read(CONFIG_FILE) @@ -2201,6 +2227,7 @@ def read_options(cmdlineargs, environment): return options, hostname, port + def setup_cqlruleset(cqlmodule): global cqlruleset cqlruleset = cqlmodule.CqlRuleSet @@ -2209,10 +2236,12 @@ def setup_cqlruleset(cqlmodule): cqlruleset.completer_for(rulename, termname)(func) cqlruleset.commands_end_with_newline.update(my_commands_ending_with_newline) + def setup_cqldocs(cqlmodule): global cqldocs cqldocs = cqlmodule.cqldocs + def init_history(): if readline is not None: try: @@ -2224,6 +2253,7 @@ def init_history(): delims += '.' readline.set_completer_delims(delims) + def save_history(): if readline is not None: try: @@ -2231,6 +2261,7 @@ def save_history(): except IOError: pass + def main(options, hostname, port): setup_cqlruleset(options.cqlmodule) setup_cqldocs(options.cqlmodule) @@ -2284,6 +2315,7 @@ def main(options, hostname, port): if batch_mode and shell.statement_error: sys.exit(2) + if __name__ == '__main__': main(*read_options(sys.argv[1:], os.environ))